Merge branch 'muc-improvements' into 'master'

MUC improvements

See merge request poezio/slixmpp!70
This commit is contained in:
mathieui 2020-12-03 23:50:14 +01:00
commit 56f44dc47d
2 changed files with 70 additions and 26 deletions

View file

@ -25,6 +25,7 @@ from slixmpp.plugins import BasePlugin
from slixmpp.xmlstream import register_stanza_plugin, ET from slixmpp.xmlstream import register_stanza_plugin, ET
from slixmpp.xmlstream.handler.callback import Callback from slixmpp.xmlstream.handler.callback import Callback
from slixmpp.xmlstream.matcher.xpath import MatchXPath from slixmpp.xmlstream.matcher.xpath import MatchXPath
from slixmpp.xmlstream.matcher.stanzapath import StanzaPath
from slixmpp.xmlstream.matcher.xmlmask import MatchXMLMask from slixmpp.xmlstream.matcher.xmlmask import MatchXMLMask
from slixmpp.exceptions import IqError, IqTimeout from slixmpp.exceptions import IqError, IqTimeout
@ -38,6 +39,7 @@ from slixmpp.plugins.xep_0045.stanza import (
MUCHistory, MUCHistory,
MUCOwnerQuery, MUCOwnerQuery,
MUCOwnerDestroy, MUCOwnerDestroy,
MUCStatus,
) )
@ -62,6 +64,8 @@ class XEP_0045(BasePlugin):
self.rooms = {} self.rooms = {}
self.our_nicks = {} self.our_nicks = {}
# load MUC support in presence stanzas # load MUC support in presence stanzas
register_stanza_plugin(MUCMessage, MUCStatus)
register_stanza_plugin(MUCPresence, MUCStatus)
register_stanza_plugin(Presence, MUCPresence) register_stanza_plugin(Presence, MUCPresence)
register_stanza_plugin(Presence, MUCJoin) register_stanza_plugin(Presence, MUCJoin)
register_stanza_plugin(MUCJoin, MUCHistory) register_stanza_plugin(MUCJoin, MUCHistory)
@ -99,23 +103,21 @@ class XEP_0045(BasePlugin):
self.xmpp.register_handler( self.xmpp.register_handler(
Callback( Callback(
'MUCConfig', 'MUCConfig',
MatchXMLMask( StanzaPath('message/muc/status'),
"<message xmlns='%s' type='groupchat'>"
"<x xmlns='http://jabber.org/protocol/muc#user'><status/></x>"
"</message>" % self.xmpp.default_ns
),
self.handle_config_change self.handle_config_change
)) ))
self.xmpp.register_handler( self.xmpp.register_handler(
Callback( Callback(
'MUCInvite', 'MUCInvite',
MatchXPath("{%s}message/{%s}x/{%s}invite" % ( StanzaPath('message/muc/invite'),
self.xmpp.default_ns,
stanza.NS_USER,
stanza.NS_USER
)),
self.handle_groupchat_invite self.handle_groupchat_invite
)) ))
self.xmpp.register_handler(
Callback(
'MUCDecline',
StanzaPath('message/muc/decline'),
self.handle_groupchat_decline
))
def plugin_end(self): def plugin_end(self):
self.xmpp.plugin['xep_0030'].del_feature(feature=stanza.NS) self.xmpp.plugin['xep_0030'].del_feature(feature=stanza.NS)
@ -124,12 +126,15 @@ class XEP_0045(BasePlugin):
self.xmpp.plugin['xep_0030'].add_feature(stanza.NS) self.xmpp.plugin['xep_0030'].add_feature(stanza.NS)
def handle_groupchat_invite(self, inv): def handle_groupchat_invite(self, inv):
""" Handle an invite into a muc. """ Handle an invite into a muc. """
"""
logging.debug("MUC invite to %s from %s: %s", inv['to'], inv["from"], inv)
if inv['from'] not in self.rooms.keys(): if inv['from'] not in self.rooms.keys():
self.xmpp.event("groupchat_invite", inv) self.xmpp.event("groupchat_invite", inv)
def handle_groupchat_decline(self, decl):
"""Handle an invitation decline."""
if decl['from'] in self.room.keys():
self.xmpp.event('groupchat_decline', decl)
def handle_config_change(self, msg): def handle_config_change(self, msg):
"""Handle a MUC configuration change (with status code).""" """Handle a MUC configuration change (with status code)."""
self.xmpp.event('groupchat_config_status', msg) self.xmpp.event('groupchat_config_status', msg)
@ -221,6 +226,7 @@ class XEP_0045(BasePlugin):
async def destroy(self, room: JID, reason='', altroom='', *, async def destroy(self, room: JID, reason='', altroom='', *,
ifrom: Optional[JID] = None, **iqkwargs) -> Iq: ifrom: Optional[JID] = None, **iqkwargs) -> Iq:
"""Destroy a room."""
iq = self.xmpp.make_iq_set(ifrom=ifrom, ito=room) iq = self.xmpp.make_iq_set(ifrom=ifrom, ito=room)
iq.enable('mucowner_query') iq.enable('mucowner_query')
iq['mucowner_query'].enable('destroy') iq['mucowner_query'].enable('destroy')
@ -265,16 +271,24 @@ class XEP_0045(BasePlugin):
iq['mucadmin_query'].append(item) iq['mucadmin_query'].append(item)
await iq.send(**iqkwargs) await iq.send(**iqkwargs)
def invite(self, room: JID, jid: JID, reason='', *, def invite(self, room: JID, jid: JID, reason: str = '', *,
mfrom: Optional[JID] = None): mfrom: Optional[JID] = None):
""" Invite a jid to a room.""" """ Invite a jid to a room."""
msg = self.xmpp.make_message(room, mfrom=mfrom) msg = self.xmpp.make_message(room, mfrom=mfrom)
msg.enable('muc') msg['muc']['invite']['to'] = jid
msg['muc']['invite'] = jid
if reason: if reason:
msg['muc']['invite']['reason'] = reason msg['muc']['invite']['reason'] = reason
self.xmpp.send(msg) self.xmpp.send(msg)
def decline(self, room: JID, jid: JID, reason: str = '', *,
mfrom: Optional[JID] = None):
"""Decline a mediated invitation."""
msg = self.xmpp.make_message(room, mfrom=mfrom)
msg['muc']['decline']['to'] = jid
if reason:
msg['muc']['decline']['reason'] = reason
self.xmpp.send(msg)
def leave_muc(self, room: JID, nick: str, msg='', pfrom=None): def leave_muc(self, room: JID, nick: str, msg='', pfrom=None):
""" Leave the specified room. """ Leave the specified room.
""" """
@ -284,7 +298,6 @@ class XEP_0045(BasePlugin):
self.xmpp.send_presence(pshow='unavailable', pto="%s/%s" % (room, nick), pfrom=pfrom) self.xmpp.send_presence(pshow='unavailable', pto="%s/%s" % (room, nick), pfrom=pfrom)
del self.rooms[room] del self.rooms[room]
async def get_room_config(self, room: JID, ifrom=''): async def get_room_config(self, room: JID, ifrom=''):
"""Get the room config form in 0004 plugin format """ """Get the room config form in 0004 plugin format """
iq = self.xmpp.make_iq_get(stanza.NS_OWNER, ito=room, ifrom=ifrom) iq = self.xmpp.make_iq_get(stanza.NS_OWNER, ito=room, ifrom=ifrom)
@ -341,7 +354,7 @@ class XEP_0045(BasePlugin):
return await iq.send(**iqkwargs) return await iq.send(**iqkwargs)
async def send_role_list(self, room: JID, roles: List[Tuple[str, str]], *, async def send_role_list(self, room: JID, roles: List[Tuple[str, str]], *,
ifrom: Optional[JID], **iqkwargs) -> Iq: ifrom: Optional[JID] = None, **iqkwargs) -> Iq:
"""Send a role delta list""" """Send a role delta list"""
iq = self.xmpp.make_iq_set(ito=room, ifrom=ifrom) iq = self.xmpp.make_iq_set(ito=room, ifrom=ifrom)
for nick, affiliation in roles: for nick, affiliation in roles:

View file

@ -7,6 +7,7 @@
See the file LICENSE for copying permission. See the file LICENSE for copying permission.
""" """
from typing import Iterable, Set
import logging import logging
from slixmpp.xmlstream import ElementBase, ET, JID from slixmpp.xmlstream import ElementBase, ET, JID
@ -23,7 +24,26 @@ class MUCBase(ElementBase):
name = 'x' name = 'x'
namespace = NS_USER namespace = NS_USER
plugin_attrib = 'muc' plugin_attrib = 'muc'
interfaces = {'affiliation', 'role', 'jid', 'nick', 'room'} interfaces = {'affiliation', 'role', 'jid', 'nick', 'room', 'status_codes'}
def get_status_codes(self) -> Set[str]:
status = self.xml.findall(f'{{{NS_USER}}}status')
return {int(status.attrib['code']) for status in status}
def set_status_codes(self, codes: Iterable[int]):
self.del_status_codes()
for code in set(codes):
self._add_status_code(code)
def del_status_codes(self):
status = self.xml.findall(f'{{{NS_USER}}}status')
for elem in status:
self.xml.remove(elem)
def _add_status_code(self, code: int):
status = MUCStatus()
status['code'] = code
self.append(status)
def get_item_attr(self, attr, default: str): def get_item_attr(self, attr, default: str):
item = self.xml.find(f'{{{NS_USER}}}item') item = self.xml.find(f'{{{NS_USER}}}item')
@ -49,12 +69,10 @@ class MUCBase(ElementBase):
def set_affiliation(self, value): def set_affiliation(self, value):
self.set_item_attr('affiliation', value) self.set_item_attr('affiliation', value)
return self
def del_affiliation(self): def del_affiliation(self):
# TODO: set default affiliation # TODO: set default affiliation
self.del_item_attr('affiliation') self.del_item_attr('affiliation')
return self
def get_jid(self): def get_jid(self):
return JID(self.get_item_attr('jid', '')) return JID(self.get_item_attr('jid', ''))
@ -63,11 +81,9 @@ class MUCBase(ElementBase):
if not isinstance(value, str): if not isinstance(value, str):
value = str(value) value = str(value)
self.set_item_attr('jid', value) self.set_item_attr('jid', value)
return self
def del_jid(self): def del_jid(self):
self.del_item_attr('jid') self.del_item_attr('jid')
return self
def get_role(self): def get_role(self):
return self.get_item_attr('role', '') return self.get_item_attr('role', '')
@ -75,12 +91,10 @@ class MUCBase(ElementBase):
def set_role(self, value): def set_role(self, value):
# TODO: check for valid role # TODO: check for valid role
self.set_item_attr('role', value) self.set_item_attr('role', value)
return self
def del_role(self): def del_role(self):
# TODO: set default role # TODO: set default role
self.del_item_attr('role') self.del_item_attr('role')
return self
def get_nick(self): def get_nick(self):
return self.parent()['from'].resource return self.parent()['from'].resource
@ -160,7 +174,15 @@ class MUCInvite(ElementBase):
name = 'invite' name = 'invite'
plugin_attrib = 'invite' plugin_attrib = 'invite'
namespace = NS_USER namespace = NS_USER
interfaces = {'to', 'reason'} interfaces = {'to', 'from', 'reason'}
sub_interfaces = {'reason'}
class MUCDecline(ElementBase):
name = 'decline'
plugin_attrib = 'decline'
namespace = NS_USER
interfaces = {'to', 'from', 'reason'}
sub_interfaces = {'reason'} sub_interfaces = {'reason'}
@ -196,3 +218,12 @@ class MUCAdminItem(ElementBase):
plugin_attrib = 'item' plugin_attrib = 'item'
interfaces = {'role', 'affiliation', 'nick', 'jid'} interfaces = {'role', 'affiliation', 'nick', 'jid'}
class MUCStatus(ElementBase):
namespace = NS_USER
name = 'status'
plugin_attrib = 'status'
interfaces = {'code'}
def set_code(self, code: int):
self.xml.attrib['code'] = str(code)