summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormathieui <mathieui@mathieui.net>2021-01-31 18:17:54 +0100
committermathieui <mathieui@mathieui.net>2021-01-31 18:17:54 +0100
commit1f97462391a18a0e45a88ec866bb6c3cc76b04a6 (patch)
tree6c385b99e4e35d7680e980a0a23cbd41db986f23
parent8689212602e29e28eb7cde000571fdbb7a437e2a (diff)
parentc7d87a27e18aa15d317fb8b8c262f0567f3d1253 (diff)
downloadslixmpp-1f97462391a18a0e45a88ec866bb6c3cc76b04a6.tar.gz
slixmpp-1f97462391a18a0e45a88ec866bb6c3cc76b04a6.tar.bz2
slixmpp-1f97462391a18a0e45a88ec866bb6c3cc76b04a6.tar.xz
slixmpp-1f97462391a18a0e45a88ec866bb6c3cc76b04a6.zip
Merge branch 'muc-sync-join' into 'master'
XEP-0045: Add new directed events and a join_muc_wait function See merge request poezio/slixmpp!111
-rw-r--r--slixmpp/exceptions.py14
-rw-r--r--slixmpp/plugins/xep_0045/muc.py85
-rw-r--r--slixmpp/xmlstream/xmlstream.py15
3 files changed, 112 insertions, 2 deletions
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
diff --git a/slixmpp/plugins/xep_0045/muc.py b/slixmpp/plugins/xep_0045/muc.py
index 905e0f49..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 (
@@ -91,6 +94,9 @@ class XEP_0045(BasePlugin):
StanzaPath("presence/muc"),
self.handle_groupchat_presence,
))
+ # <x xmlns="http://jabber.org/protocol/muc"/> 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 +104,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(
@@ -184,12 +197,18 @@ 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)
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:
@@ -238,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.
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)