From e9b07e32bb44464d71371eff8b93be9635777bbd Mon Sep 17 00:00:00 2001 From: mathieui Date: Sun, 31 Jan 2021 17:00:42 +0100 Subject: [PATCH 1/5] exceptions: add a PresenceError exception --- slixmpp/exceptions.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/slixmpp/exceptions.py b/slixmpp/exceptions.py index 0486666e..5ec6e7e1 100644 --- a/slixmpp/exceptions.py +++ b/slixmpp/exceptions.py @@ -101,3 +101,17 @@ class IqError(XMPPError): #: The :class:`~slixmpp.stanza.iq.Iq` error result stanza. self.iq = iq + + +class PresenceError(XMPPError): + """ + An exception raised in specific circumstances for presences + of type 'error' received. + """ + def __init__(self, pres): + super().__init__( + condition=pres['error']['condition'], + text=pres['error']['text'], + etype=pres['error']['type'], + ) + self.presence = pres From 7932a03378f9774b0a133ce71ce65e732b5d3994 Mon Sep 17 00:00:00 2001 From: mathieui Date: Sun, 31 Jan 2021 17:01:41 +0100 Subject: [PATCH 2/5] xmlstream: add a simple contextmanager for temporary events --- slixmpp/xmlstream/xmlstream.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/slixmpp/xmlstream/xmlstream.py b/slixmpp/xmlstream/xmlstream.py index 5074aa8c..b80c55d3 100644 --- a/slixmpp/xmlstream/xmlstream.py +++ b/slixmpp/xmlstream/xmlstream.py @@ -30,7 +30,7 @@ import weakref import uuid from asyncio import iscoroutinefunction, wait, Future - +from contextlib import contextmanager import xml.etree.ElementTree as ET from slixmpp.xmlstream.asyncio import asyncio @@ -1208,3 +1208,16 @@ class XMLStream(asyncio.BaseProtocol): disposable=True, ) return await asyncio.wait_for(fut, timeout, loop=self.loop) + + @contextmanager + def event_handler(self, event: str, handler: Callable): + """ + Context manager that adds then removes an event handler. + """ + self.add_event_handler(event, handler) + try: + yield + except Exception as exc: + raise + finally: + self.del_event_handler(event, handler) From eda692d1c6093354e4fa13765329384e829ccac1 Mon Sep 17 00:00:00 2001 From: mathieui Date: Sun, 31 Jan 2021 17:02:51 +0100 Subject: [PATCH 3/5] XEP-0045: add a directed event for self-presence --- slixmpp/plugins/xep_0045/muc.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/slixmpp/plugins/xep_0045/muc.py b/slixmpp/plugins/xep_0045/muc.py index 905e0f49..ef6955bb 100644 --- a/slixmpp/plugins/xep_0045/muc.py +++ b/slixmpp/plugins/xep_0045/muc.py @@ -184,6 +184,8 @@ class XEP_0045(BasePlugin): self.rooms[entry['room']][entry['nick']] = entry log.debug("MUC presence from %s/%s : %s", entry['room'],entry['nick'], entry) self.xmpp.event("groupchat_presence", pr) + if 110 in pr['muc']['status_codes']: + self.xmpp.event("muc::%s::self-presence" % entry['room'], pr) self.xmpp.event("muc::%s::presence" % entry['room'], pr) if got_offline: self.xmpp.event("muc::%s::got_offline" % entry['room'], pr) From a8113dca49fe25123b256379461f46c02bbf01c8 Mon Sep 17 00:00:00 2001 From: mathieui Date: Sun, 31 Jan 2021 17:03:39 +0100 Subject: [PATCH 4/5] XEP-0045: Add a specific handler for MUC presence errors --- slixmpp/plugins/xep_0045/muc.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/slixmpp/plugins/xep_0045/muc.py b/slixmpp/plugins/xep_0045/muc.py index ef6955bb..c2806045 100644 --- a/slixmpp/plugins/xep_0045/muc.py +++ b/slixmpp/plugins/xep_0045/muc.py @@ -91,6 +91,9 @@ class XEP_0045(BasePlugin): StanzaPath("presence/muc"), self.handle_groupchat_presence, )) + # is only used in + # presence when joining on the client side, and for errors on + # the server side. if self.xmpp.is_component: self.xmpp.register_handler( Callback( @@ -98,6 +101,13 @@ class XEP_0045(BasePlugin): StanzaPath("presence/muc_join"), self.handle_groupchat_join, )) + self.xmpp.register_handler( + Callback( + "MUCPresenceError", + StanzaPath("presence@type=error/muc_join"), + self._handle_presence_error, + ) + ) self.xmpp.register_handler( Callback( @@ -192,6 +202,10 @@ class XEP_0045(BasePlugin): if got_online: self.xmpp.event("muc::%s::got_online" % entry['room'], pr) + def _handle_presence_error(self, pr: Presence): + """Generate MUC presence error events""" + self.xmpp.event("muc::%s::presence-error" % pr['from'].bare, pr) + def handle_groupchat_presence(self, pr: Presence): """ Handle a presence in a muc.""" if self.xmpp.is_component: From c7d87a27e18aa15d317fb8b8c262f0567f3d1253 Mon Sep 17 00:00:00 2001 From: mathieui Date: Sun, 31 Jan 2021 17:06:38 +0100 Subject: [PATCH 5/5] XEP-0045: Add a join_muc_wait function Which is async, raises on timeout or error, and returns when joined. --- slixmpp/plugins/xep_0045/muc.py | 69 ++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/slixmpp/plugins/xep_0045/muc.py b/slixmpp/plugins/xep_0045/muc.py index c2806045..e156ded1 100644 --- a/slixmpp/plugins/xep_0045/muc.py +++ b/slixmpp/plugins/xep_0045/muc.py @@ -8,8 +8,11 @@ """ from __future__ import with_statement +import asyncio import logging +from datetime import datetime from typing import ( + Dict, List, Tuple, Optional, @@ -27,7 +30,7 @@ from slixmpp.xmlstream.handler.callback import Callback from slixmpp.xmlstream.matcher.xpath import MatchXPath from slixmpp.xmlstream.matcher.stanzapath import StanzaPath from slixmpp.xmlstream.matcher.xmlmask import MatchXMLMask -from slixmpp.exceptions import IqError, IqTimeout +from slixmpp.exceptions import IqError, IqTimeout, PresenceError from slixmpp.plugins.xep_0045 import stanza from slixmpp.plugins.xep_0045.stanza import ( @@ -254,6 +257,70 @@ class XEP_0045(BasePlugin): return nick return None + async def join_muc_wait(self, room: JID, nick: str, *, + password: Optional[str] = None, + maxchars: Optional[int] = None, + maxstanzas: Optional[int] = None, + seconds: Optional[int] = None, + since: Optional[datetime] = None, + presence_options: Optional[Dict[str, str]] = None, + timeout: int = 30) -> Presence: + """ + Try to join a MUC and block until we are joined or get an error. + + Only one of {maxchars, maxstanzas, seconds, since} will be used, in + that order. + + :param password: The optional room password. + :param maxchars: Max number of characters to return from history. + :param maxstanzas: Max number of stanzas to return from history. + :param seconds: Fetch history until that many seconds in the past. + :param since: Fetch history since that timestamp. + :raises: A slixmpp.exceptions.PresenceError if the MUC returns a + presence error. + :raises: An asyncio.TimeoutError if there is neither success nor + presence error when the timeout is reached. + :return: Our own presence + """ + if presence_options is None: + presence_options = {} + stanza = self.xmpp.make_presence( + pto="%s/%s" % (room, nick), + **presence_options + ) + stanza.enable('muc_join') + if password is not None: + stanza['muc_join']['password'] = password + if maxchars is not None: + stanza['muc_join']['history']['maxchars'] = str(maxchars) + elif maxstanzas is not None: + stanza['muc_join']['history']['maxstanzas'] = str(maxstanzas) + elif seconds is not None: + stanza['muc_join']['history']['seconds'] = str(seconds) + elif since is not None: + fmt = self.xmpp.plugin['xep_0082'].format_datetime(since) + stanza['muc_join']['history']['since'] = fmt + self.rooms[room] = {} + self.our_nicks[room] = nick + stanza.send() + + future = asyncio.Future() + context1 = self.xmpp.event_handler("muc::%s::self-presence" % room, future.set_result) + context2 = self.xmpp.event_handler("muc::%s::presence-error" % room, future.set_result) + with context1, context2: + done, pending = await asyncio.wait( + [future], + timeout=timeout, + ) + if pending: + raise asyncio.TimeoutError() + pres = await future + if pres['type'] == 'error': + raise PresenceError(pres) + # update known nick in case it has changed + self.our_nicks[room] = pres['from'].resource + return pres + def join_muc(self, room: JID, nick: str, maxhistory="0", password='', pstatus='', pshow='', pfrom=''): """ Join the specified room, requesting 'maxhistory' lines of history.