slidge 0.2.0a8__py3-none-any.whl → 0.2.0a10__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- slidge/__version__.py +1 -1
- slidge/command/adhoc.py +1 -1
- slidge/command/base.py +4 -4
- slidge/contact/contact.py +3 -2
- slidge/contact/roster.py +7 -0
- slidge/core/dispatcher/__init__.py +3 -0
- slidge/core/{gateway → dispatcher}/caps.py +6 -4
- slidge/core/{gateway → dispatcher}/disco.py +11 -17
- slidge/core/dispatcher/message/__init__.py +10 -0
- slidge/core/dispatcher/message/chat_state.py +40 -0
- slidge/core/dispatcher/message/marker.py +67 -0
- slidge/core/dispatcher/message/message.py +397 -0
- slidge/core/dispatcher/muc/__init__.py +12 -0
- slidge/core/dispatcher/muc/admin.py +98 -0
- slidge/core/{gateway → dispatcher/muc}/mam.py +26 -15
- slidge/core/dispatcher/muc/misc.py +118 -0
- slidge/core/dispatcher/muc/owner.py +96 -0
- slidge/core/{gateway → dispatcher/muc}/ping.py +10 -15
- slidge/core/dispatcher/presence.py +177 -0
- slidge/core/{gateway → dispatcher}/registration.py +23 -2
- slidge/core/{gateway → dispatcher}/search.py +9 -14
- slidge/core/dispatcher/session_dispatcher.py +84 -0
- slidge/core/dispatcher/util.py +174 -0
- slidge/core/{gateway/vcard_temp.py → dispatcher/vcard.py} +26 -12
- slidge/core/{gateway/base.py → gateway.py} +42 -137
- slidge/core/mixins/attachment.py +7 -2
- slidge/core/mixins/base.py +2 -2
- slidge/core/mixins/message.py +10 -4
- slidge/core/pubsub.py +2 -1
- slidge/core/session.py +28 -2
- slidge/db/alembic/versions/45c24cc73c91_add_bob.py +42 -0
- slidge/db/models.py +13 -0
- slidge/db/store.py +128 -2
- slidge/{core/gateway → slixfix}/delivery_receipt.py +1 -1
- slidge/util/test.py +5 -1
- slidge/util/types.py +6 -0
- slidge/util/util.py +5 -2
- {slidge-0.2.0a8.dist-info → slidge-0.2.0a10.dist-info}/METADATA +2 -1
- {slidge-0.2.0a8.dist-info → slidge-0.2.0a10.dist-info}/RECORD +42 -33
- slidge/core/gateway/__init__.py +0 -3
- slidge/core/gateway/muc_admin.py +0 -35
- slidge/core/gateway/presence.py +0 -95
- slidge/core/gateway/session_dispatcher.py +0 -895
- {slidge-0.2.0a8.dist-info → slidge-0.2.0a10.dist-info}/LICENSE +0 -0
- {slidge-0.2.0a8.dist-info → slidge-0.2.0a10.dist-info}/WHEEL +0 -0
- {slidge-0.2.0a8.dist-info → slidge-0.2.0a10.dist-info}/entry_points.txt +0 -0
@@ -7,13 +7,16 @@ from slixmpp import JID, Iq
|
|
7
7
|
from slixmpp.exceptions import XMPPError
|
8
8
|
|
9
9
|
from ...db import GatewayUser
|
10
|
+
from .. import config
|
11
|
+
from .util import DispatcherMixin
|
10
12
|
|
11
13
|
if TYPE_CHECKING:
|
12
|
-
from .
|
14
|
+
from slidge.core.gateway import BaseGateway
|
13
15
|
|
14
16
|
|
15
|
-
class
|
17
|
+
class RegistrationMixin(DispatcherMixin):
|
16
18
|
def __init__(self, xmpp: "BaseGateway"):
|
19
|
+
super().__init__(xmpp)
|
17
20
|
self.xmpp = xmpp
|
18
21
|
xmpp["xep_0077"].api.register(
|
19
22
|
self.xmpp.make_registration_form, "make_registration_form"
|
@@ -25,6 +28,9 @@ class Registration:
|
|
25
28
|
# TODO: either fully use slixmpp internal API or rewrite registration without it at all
|
26
29
|
xmpp["xep_0077"].api.register(lambda *a: None, "user_remove")
|
27
30
|
|
31
|
+
xmpp.add_event_handler("user_register", self._on_user_register)
|
32
|
+
xmpp.add_event_handler("user_unregister", self._on_user_unregister)
|
33
|
+
|
28
34
|
def get_user(self, jid: JID) -> GatewayUser | None:
|
29
35
|
return self.xmpp.store.users.get(jid)
|
30
36
|
|
@@ -60,5 +66,20 @@ class Registration:
|
|
60
66
|
user.legacy_module_data.update(form_dict)
|
61
67
|
self.xmpp.store.users.update(user)
|
62
68
|
|
69
|
+
async def _on_user_register(self, iq: Iq):
|
70
|
+
session = await self._get_session(iq, wait_for_ready=False)
|
71
|
+
for jid in config.ADMINS:
|
72
|
+
self.xmpp.send_message(
|
73
|
+
mto=jid,
|
74
|
+
mbody=f"{iq.get_from()} has registered",
|
75
|
+
mtype="chat",
|
76
|
+
mfrom=self.xmpp.boundjid.bare,
|
77
|
+
)
|
78
|
+
session.send_gateway_message(self.xmpp.WELCOME_MESSAGE)
|
79
|
+
await self.xmpp.login_wrap(session)
|
80
|
+
|
81
|
+
async def _on_user_unregister(self, iq: Iq):
|
82
|
+
await self.xmpp.session_cls.kill_by_jid(iq.get_from())
|
83
|
+
|
63
84
|
|
64
85
|
log = logging.getLogger(__name__)
|
@@ -3,13 +3,15 @@ from typing import TYPE_CHECKING
|
|
3
3
|
from slixmpp import JID, CoroutineCallback, Iq, StanzaPath
|
4
4
|
from slixmpp.exceptions import XMPPError
|
5
5
|
|
6
|
+
from .util import DispatcherMixin, exceptions_to_xmpp_errors
|
7
|
+
|
6
8
|
if TYPE_CHECKING:
|
7
|
-
from .
|
9
|
+
from slidge.core.gateway import BaseGateway
|
8
10
|
|
9
11
|
|
10
|
-
class
|
12
|
+
class SearchMixin(DispatcherMixin):
|
11
13
|
def __init__(self, xmpp: "BaseGateway"):
|
12
|
-
|
14
|
+
super().__init__(xmpp)
|
13
15
|
|
14
16
|
xmpp["xep_0055"].api.register(self.search_get_form, "search_get_form")
|
15
17
|
xmpp["xep_0055"].api.register(self._search_query, "search_query")
|
@@ -45,13 +47,9 @@ class Search:
|
|
45
47
|
"""
|
46
48
|
Handles a search request
|
47
49
|
"""
|
48
|
-
|
49
|
-
if user is None:
|
50
|
-
raise XMPPError(text="Search is only allowed for registered users")
|
50
|
+
session = await self._get_session(iq)
|
51
51
|
|
52
|
-
result = await
|
53
|
-
iq["search"]["form"].get_values()
|
54
|
-
)
|
52
|
+
result = await session.on_search(iq["search"]["form"].get_values())
|
55
53
|
|
56
54
|
if not result:
|
57
55
|
raise XMPPError("item-not-found", text="Nothing was found")
|
@@ -64,19 +62,17 @@ class Search:
|
|
64
62
|
form.add_item(item)
|
65
63
|
return reply
|
66
64
|
|
65
|
+
@exceptions_to_xmpp_errors
|
67
66
|
async def _handle_gateway_iq(self, iq: Iq):
|
68
67
|
if iq.get_to() != self.xmpp.boundjid.bare:
|
69
68
|
raise XMPPError("bad-request", "This can only be used on the component JID")
|
70
69
|
|
71
|
-
user = self.xmpp.store.users.get(iq.get_from())
|
72
|
-
if user is None:
|
73
|
-
raise XMPPError("not-authorized", "Register to the gateway first")
|
74
|
-
|
75
70
|
if len(self.xmpp.SEARCH_FIELDS) > 1:
|
76
71
|
raise XMPPError(
|
77
72
|
"feature-not-implemented", "Use jabber search for this gateway"
|
78
73
|
)
|
79
74
|
|
75
|
+
session = await self._get_session(iq)
|
80
76
|
field = self.xmpp.SEARCH_FIELDS[0]
|
81
77
|
|
82
78
|
reply = iq.reply()
|
@@ -85,7 +81,6 @@ class Search:
|
|
85
81
|
reply["gateway"]["prompt"] = field.label
|
86
82
|
elif iq["type"] == "set":
|
87
83
|
prompt = iq["gateway"]["prompt"]
|
88
|
-
session = self.xmpp.session_cls.from_user(user)
|
89
84
|
result = await session.on_search({field.var: prompt})
|
90
85
|
if result is None or not result.items:
|
91
86
|
raise XMPPError(
|
@@ -0,0 +1,84 @@
|
|
1
|
+
import logging
|
2
|
+
from typing import TYPE_CHECKING
|
3
|
+
|
4
|
+
from slixmpp import Message
|
5
|
+
from slixmpp.exceptions import IqError
|
6
|
+
from slixmpp.plugins.xep_0084.stanza import Info
|
7
|
+
|
8
|
+
from ..session import BaseSession
|
9
|
+
from .caps import CapsMixin
|
10
|
+
from .disco import DiscoMixin
|
11
|
+
from .message import MessageMixin
|
12
|
+
from .muc import MucMixin
|
13
|
+
from .presence import PresenceHandlerMixin
|
14
|
+
from .registration import RegistrationMixin
|
15
|
+
from .search import SearchMixin
|
16
|
+
from .util import exceptions_to_xmpp_errors
|
17
|
+
from .vcard import VCardMixin
|
18
|
+
|
19
|
+
if TYPE_CHECKING:
|
20
|
+
from slidge.core.gateway import BaseGateway
|
21
|
+
|
22
|
+
|
23
|
+
class SessionDispatcher(
|
24
|
+
CapsMixin,
|
25
|
+
DiscoMixin,
|
26
|
+
RegistrationMixin,
|
27
|
+
MessageMixin,
|
28
|
+
MucMixin,
|
29
|
+
PresenceHandlerMixin,
|
30
|
+
SearchMixin,
|
31
|
+
VCardMixin,
|
32
|
+
):
|
33
|
+
def __init__(self, xmpp: "BaseGateway"):
|
34
|
+
super().__init__(xmpp)
|
35
|
+
xmpp.add_event_handler(
|
36
|
+
"avatar_metadata_publish", self.on_avatar_metadata_publish
|
37
|
+
)
|
38
|
+
|
39
|
+
@exceptions_to_xmpp_errors
|
40
|
+
async def on_avatar_metadata_publish(self, m: Message):
|
41
|
+
session = await self._get_session(m, timeout=None)
|
42
|
+
if not session.user.preferences.get("sync_avatar", False):
|
43
|
+
session.log.debug("User does not want to sync their avatar")
|
44
|
+
return
|
45
|
+
info = m["pubsub_event"]["items"]["item"]["avatar_metadata"]["info"]
|
46
|
+
|
47
|
+
await self.on_avatar_metadata_info(session, info)
|
48
|
+
|
49
|
+
async def on_avatar_metadata_info(self, session: BaseSession, info: Info):
|
50
|
+
hash_ = info["id"]
|
51
|
+
|
52
|
+
if session.user.avatar_hash == hash_:
|
53
|
+
session.log.debug("We already know this avatar hash")
|
54
|
+
return
|
55
|
+
self.xmpp.store.users.set_avatar_hash(session.user_pk, None)
|
56
|
+
|
57
|
+
if hash_:
|
58
|
+
try:
|
59
|
+
iq = await self.xmpp.plugin["xep_0084"].retrieve_avatar(
|
60
|
+
session.user_jid, hash_, ifrom=self.xmpp.boundjid.bare
|
61
|
+
)
|
62
|
+
except IqError as e:
|
63
|
+
session.log.warning("Could not fetch the user's avatar: %s", e)
|
64
|
+
return
|
65
|
+
bytes_ = iq["pubsub"]["items"]["item"]["avatar_data"]["value"]
|
66
|
+
type_ = info["type"]
|
67
|
+
height = info["height"]
|
68
|
+
width = info["width"]
|
69
|
+
else:
|
70
|
+
bytes_ = type_ = height = width = hash_ = None
|
71
|
+
try:
|
72
|
+
await session.on_avatar(bytes_, hash_, type_, width, height)
|
73
|
+
except NotImplementedError:
|
74
|
+
pass
|
75
|
+
except Exception as e:
|
76
|
+
# If something goes wrong here, replying an error stanza will to the
|
77
|
+
# avatar update will likely not show in most clients, so let's send
|
78
|
+
# a normal message from the component to the user.
|
79
|
+
session.send_gateway_message(
|
80
|
+
f"Something went wrong trying to set your avatar: {e!r}"
|
81
|
+
)
|
82
|
+
|
83
|
+
|
84
|
+
log = logging.getLogger(__name__)
|
@@ -0,0 +1,174 @@
|
|
1
|
+
import logging
|
2
|
+
from functools import wraps
|
3
|
+
from typing import TYPE_CHECKING, Any, Awaitable, Callable, TypeVar
|
4
|
+
|
5
|
+
from slixmpp import JID, Iq, Message, Presence
|
6
|
+
from slixmpp.exceptions import XMPPError
|
7
|
+
from slixmpp.xmlstream import StanzaBase
|
8
|
+
|
9
|
+
from ...util.types import Recipient, RecipientType
|
10
|
+
from ..session import BaseSession
|
11
|
+
|
12
|
+
if TYPE_CHECKING:
|
13
|
+
from slidge import BaseGateway
|
14
|
+
from slidge.group import LegacyMUC
|
15
|
+
|
16
|
+
|
17
|
+
class Ignore(BaseException):
|
18
|
+
pass
|
19
|
+
|
20
|
+
|
21
|
+
class DispatcherMixin:
|
22
|
+
def __init__(self, xmpp: "BaseGateway"):
|
23
|
+
self.xmpp = xmpp
|
24
|
+
|
25
|
+
async def _get_session(
|
26
|
+
self,
|
27
|
+
stanza: Message | Presence | Iq,
|
28
|
+
timeout: int | None = 10,
|
29
|
+
wait_for_ready=True,
|
30
|
+
logged=False,
|
31
|
+
) -> BaseSession:
|
32
|
+
xmpp = self.xmpp
|
33
|
+
if stanza.get_from().server == xmpp.boundjid.bare:
|
34
|
+
log.debug("Ignoring echo")
|
35
|
+
raise Ignore
|
36
|
+
if (
|
37
|
+
isinstance(stanza, Message)
|
38
|
+
and stanza.get_type() == "chat"
|
39
|
+
and stanza.get_to() == xmpp.boundjid.bare
|
40
|
+
):
|
41
|
+
log.debug("Ignoring message to component")
|
42
|
+
raise Ignore
|
43
|
+
session = await self._get_session_from_jid(
|
44
|
+
stanza.get_from(), timeout, wait_for_ready, logged
|
45
|
+
)
|
46
|
+
if isinstance(stanza, Message) and _ignore(session, stanza):
|
47
|
+
raise Ignore
|
48
|
+
return session
|
49
|
+
|
50
|
+
async def _get_session_from_jid(
|
51
|
+
self,
|
52
|
+
jid: JID,
|
53
|
+
timeout: int | None = 10,
|
54
|
+
wait_for_ready=True,
|
55
|
+
logged=False,
|
56
|
+
) -> BaseSession:
|
57
|
+
session = self.xmpp.get_session_from_jid(jid)
|
58
|
+
if session is None:
|
59
|
+
raise XMPPError("registration-required")
|
60
|
+
if logged:
|
61
|
+
session.raise_if_not_logged()
|
62
|
+
if wait_for_ready:
|
63
|
+
await session.wait_for_ready(timeout)
|
64
|
+
return session
|
65
|
+
|
66
|
+
async def get_muc_from_stanza(self, iq: Iq | Message | Presence) -> "LegacyMUC":
|
67
|
+
ito = iq.get_to()
|
68
|
+
if ito == self.xmpp.boundjid.bare:
|
69
|
+
raise XMPPError("bad-request", text="This is only handled for MUCs")
|
70
|
+
|
71
|
+
session = await self._get_session(iq, logged=True)
|
72
|
+
muc = await session.bookmarks.by_jid(ito)
|
73
|
+
return muc
|
74
|
+
|
75
|
+
def _xmpp_msg_id_to_legacy(self, session: "BaseSession", xmpp_id: str):
|
76
|
+
sent = self.xmpp.store.sent.get_legacy_id(session.user_pk, xmpp_id)
|
77
|
+
if sent is not None:
|
78
|
+
return self.xmpp.LEGACY_MSG_ID_TYPE(sent)
|
79
|
+
|
80
|
+
multi = self.xmpp.store.multi.get_legacy_id(session.user_pk, xmpp_id)
|
81
|
+
if multi:
|
82
|
+
return self.xmpp.LEGACY_MSG_ID_TYPE(multi)
|
83
|
+
|
84
|
+
try:
|
85
|
+
return session.xmpp_to_legacy_msg_id(xmpp_id)
|
86
|
+
except XMPPError:
|
87
|
+
raise
|
88
|
+
except Exception as e:
|
89
|
+
log.debug("Couldn't convert xmpp msg ID to legacy ID.", exc_info=e)
|
90
|
+
raise XMPPError(
|
91
|
+
"internal-server-error", "Couldn't convert xmpp msg ID to legacy ID."
|
92
|
+
)
|
93
|
+
|
94
|
+
async def _get_session_entity_thread(
|
95
|
+
self, msg: Message
|
96
|
+
) -> tuple["BaseSession", Recipient, int | str]:
|
97
|
+
session = await self._get_session(msg)
|
98
|
+
e: Recipient = await _get_entity(session, msg)
|
99
|
+
legacy_thread = await _xmpp_to_legacy_thread(session, msg, e)
|
100
|
+
return session, e, legacy_thread
|
101
|
+
|
102
|
+
|
103
|
+
def _ignore(session: "BaseSession", msg: Message):
|
104
|
+
i = msg.get_id()
|
105
|
+
if i.startswith("slidge-carbon-"):
|
106
|
+
return True
|
107
|
+
if i not in session.ignore_messages:
|
108
|
+
return False
|
109
|
+
session.log.debug("Ignored sent carbon: %s", i)
|
110
|
+
session.ignore_messages.remove(i)
|
111
|
+
return True
|
112
|
+
|
113
|
+
|
114
|
+
async def _xmpp_to_legacy_thread(
|
115
|
+
session: "BaseSession", msg: Message, recipient: RecipientType
|
116
|
+
):
|
117
|
+
xmpp_thread = msg["thread"]
|
118
|
+
if not xmpp_thread:
|
119
|
+
return
|
120
|
+
|
121
|
+
if session.MESSAGE_IDS_ARE_THREAD_IDS:
|
122
|
+
return session.xmpp.store.sent.get_legacy_thread(session.user_pk, xmpp_thread)
|
123
|
+
|
124
|
+
async with session.thread_creation_lock:
|
125
|
+
legacy_thread_str = session.xmpp.store.sent.get_legacy_thread(
|
126
|
+
session.user_pk, xmpp_thread
|
127
|
+
)
|
128
|
+
if legacy_thread_str is None:
|
129
|
+
legacy_thread = str(await recipient.create_thread(xmpp_thread))
|
130
|
+
session.xmpp.store.sent.set_thread(
|
131
|
+
session.user_pk, xmpp_thread, legacy_thread
|
132
|
+
)
|
133
|
+
return session.xmpp.LEGACY_MSG_ID_TYPE(legacy_thread)
|
134
|
+
|
135
|
+
|
136
|
+
async def _get_entity(session: "BaseSession", m: Message) -> RecipientType:
|
137
|
+
session.raise_if_not_logged()
|
138
|
+
if m.get_type() == "groupchat":
|
139
|
+
muc = await session.bookmarks.by_jid(m.get_to())
|
140
|
+
r = m.get_from().resource
|
141
|
+
if r not in muc.get_user_resources():
|
142
|
+
session.create_task(muc.kick_resource(r))
|
143
|
+
raise XMPPError("not-acceptable", "You are not connected to this chat")
|
144
|
+
return muc
|
145
|
+
else:
|
146
|
+
return await session.contacts.by_jid(m.get_to())
|
147
|
+
|
148
|
+
|
149
|
+
StanzaType = TypeVar("StanzaType", bound=StanzaBase)
|
150
|
+
HandlerType = Callable[[Any, StanzaType], Awaitable[None]]
|
151
|
+
|
152
|
+
|
153
|
+
def exceptions_to_xmpp_errors(cb: HandlerType) -> HandlerType:
|
154
|
+
@wraps(cb)
|
155
|
+
async def wrapped(*args):
|
156
|
+
try:
|
157
|
+
await cb(*args)
|
158
|
+
except Ignore:
|
159
|
+
pass
|
160
|
+
except XMPPError:
|
161
|
+
raise
|
162
|
+
except NotImplementedError:
|
163
|
+
log.debug("NotImplementedError raised in %s", cb)
|
164
|
+
raise XMPPError(
|
165
|
+
"feature-not-implemented", "Not implemented by the legacy module"
|
166
|
+
)
|
167
|
+
except Exception as e:
|
168
|
+
log.error("Failed to handle incoming stanza: %s", args, exc_info=e)
|
169
|
+
raise XMPPError("internal-server-error", str(e))
|
170
|
+
|
171
|
+
return wrapped
|
172
|
+
|
173
|
+
|
174
|
+
log = logging.getLogger(__name__)
|
@@ -1,5 +1,4 @@
|
|
1
1
|
from copy import copy
|
2
|
-
from typing import TYPE_CHECKING
|
3
2
|
|
4
3
|
from slixmpp import CoroutineCallback, Iq, StanzaPath, register_stanza_plugin
|
5
4
|
from slixmpp.exceptions import XMPPError
|
@@ -9,21 +8,23 @@ from slixmpp.plugins.xep_0292.stanza import NS as VCard4NS
|
|
9
8
|
from ...contact import LegacyContact
|
10
9
|
from ...core.session import BaseSession
|
11
10
|
from ...group import LegacyParticipant
|
11
|
+
from .util import DispatcherMixin, exceptions_to_xmpp_errors
|
12
12
|
|
13
|
-
if TYPE_CHECKING:
|
14
|
-
from .base import BaseGateway
|
15
13
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
14
|
+
class VCardMixin(DispatcherMixin):
|
15
|
+
def __init__(self, xmpp):
|
16
|
+
super().__init__(xmpp)
|
17
|
+
xmpp.register_handler(
|
18
|
+
CoroutineCallback(
|
19
|
+
"get_vcard", StanzaPath("iq@type=get/vcard"), self.on_get_vcard
|
20
|
+
)
|
21
|
+
)
|
22
|
+
xmpp.remove_handler("VCardTemp")
|
22
23
|
xmpp.register_handler(
|
23
24
|
CoroutineCallback(
|
24
25
|
"VCardTemp",
|
25
26
|
StanzaPath("iq/vcard_temp"),
|
26
|
-
self.
|
27
|
+
self.__vcard_temp_handler,
|
27
28
|
)
|
28
29
|
)
|
29
30
|
# TODO: MR to slixmpp adding this to XEP-0084
|
@@ -32,7 +33,20 @@ class VCardTemp:
|
|
32
33
|
MetaData,
|
33
34
|
)
|
34
35
|
|
35
|
-
|
36
|
+
@exceptions_to_xmpp_errors
|
37
|
+
async def on_get_vcard(self, iq: Iq):
|
38
|
+
session = await self._get_session(iq, logged=True)
|
39
|
+
contact = await session.contacts.by_jid(iq.get_to())
|
40
|
+
vcard = await contact.get_vcard()
|
41
|
+
reply = iq.reply()
|
42
|
+
if vcard:
|
43
|
+
reply.append(vcard)
|
44
|
+
else:
|
45
|
+
reply.enable("vcard")
|
46
|
+
reply.send()
|
47
|
+
|
48
|
+
@exceptions_to_xmpp_errors
|
49
|
+
async def __vcard_temp_handler(self, iq: Iq):
|
36
50
|
if iq["type"] == "get":
|
37
51
|
return await self.__handle_get_vcard_temp(iq)
|
38
52
|
|
@@ -105,7 +119,7 @@ class VCardTemp:
|
|
105
119
|
reply.send()
|
106
120
|
|
107
121
|
async def __handle_set_vcard_temp(self, iq: Iq):
|
108
|
-
muc = await self.
|
122
|
+
muc = await self.get_muc_from_stanza(iq)
|
109
123
|
to = iq.get_to()
|
110
124
|
|
111
125
|
if to.resource:
|