slidge 0.2.0a9__py3-none-any.whl → 0.2.0a10__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- slidge/__version__.py +1 -1
- slidge/command/adhoc.py +1 -1
- slidge/command/base.py +4 -4
- 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/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.0a9.dist-info → slidge-0.2.0a10.dist-info}/METADATA +2 -1
- {slidge-0.2.0a9.dist-info → slidge-0.2.0a10.dist-info}/RECORD +40 -31
- 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.0a9.dist-info → slidge-0.2.0a10.dist-info}/LICENSE +0 -0
- {slidge-0.2.0a9.dist-info → slidge-0.2.0a10.dist-info}/WHEEL +0 -0
- {slidge-0.2.0a9.dist-info → slidge-0.2.0a10.dist-info}/entry_points.txt +0 -0
@@ -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:
|
@@ -8,16 +8,7 @@ import re
|
|
8
8
|
import tempfile
|
9
9
|
from copy import copy
|
10
10
|
from datetime import datetime
|
11
|
-
from typing import
|
12
|
-
TYPE_CHECKING,
|
13
|
-
Any,
|
14
|
-
Callable,
|
15
|
-
Collection,
|
16
|
-
Mapping,
|
17
|
-
Optional,
|
18
|
-
Sequence,
|
19
|
-
Union,
|
20
|
-
)
|
11
|
+
from typing import TYPE_CHECKING, Any, Callable, Mapping, Optional, Sequence, Union
|
21
12
|
|
22
13
|
import aiohttp
|
23
14
|
import qrcode
|
@@ -27,40 +18,30 @@ from slixmpp.plugins.xep_0060.stanza import OwnerAffiliation
|
|
27
18
|
from slixmpp.types import MessageTypes
|
28
19
|
from slixmpp.xmlstream.xmlstream import NotConnectedError
|
29
20
|
|
30
|
-
from
|
31
|
-
from
|
32
|
-
from
|
33
|
-
from
|
34
|
-
from
|
35
|
-
from
|
36
|
-
from
|
37
|
-
from
|
38
|
-
from
|
39
|
-
from
|
40
|
-
from
|
41
|
-
from
|
42
|
-
from
|
43
|
-
from
|
44
|
-
from
|
45
|
-
from
|
46
|
-
from .
|
47
|
-
from .
|
48
|
-
from .disco import Disco
|
49
|
-
from .mam import Mam
|
50
|
-
from .muc_admin import MucAdmin
|
51
|
-
from .ping import Ping
|
52
|
-
from .presence import PresenceHandlerMixin
|
53
|
-
from .registration import Registration
|
54
|
-
from .search import Search
|
55
|
-
from .session_dispatcher import SessionDispatcher
|
56
|
-
from .vcard_temp import VCardTemp
|
21
|
+
from slidge import command # noqa: F401
|
22
|
+
from slidge.command.adhoc import AdhocProvider
|
23
|
+
from slidge.command.admin import Exec
|
24
|
+
from slidge.command.base import Command, FormField
|
25
|
+
from slidge.command.chat_command import ChatCommandProvider
|
26
|
+
from slidge.command.register import RegistrationType
|
27
|
+
from slidge.core import config
|
28
|
+
from slidge.core.dispatcher.session_dispatcher import SessionDispatcher
|
29
|
+
from slidge.core.mixins import MessageMixin
|
30
|
+
from slidge.core.pubsub import PubSubComponent
|
31
|
+
from slidge.core.session import BaseSession
|
32
|
+
from slidge.db import GatewayUser, SlidgeStore
|
33
|
+
from slidge.db.avatar import avatar_cache
|
34
|
+
from slidge.slixfix.delivery_receipt import DeliveryReceipt
|
35
|
+
from slidge.slixfix.roster import RosterBackend
|
36
|
+
from slidge.util import ABCSubclassableOnceAtMost
|
37
|
+
from slidge.util.types import AvatarType, MessageOrPresenceTypeVar
|
38
|
+
from slidge.util.util import timeit
|
57
39
|
|
58
40
|
if TYPE_CHECKING:
|
59
|
-
|
41
|
+
pass
|
60
42
|
|
61
43
|
|
62
44
|
class BaseGateway(
|
63
|
-
PresenceHandlerMixin,
|
64
45
|
ComponentXMPP,
|
65
46
|
MessageMixin,
|
66
47
|
metaclass=ABCSubclassableOnceAtMost,
|
@@ -118,7 +99,7 @@ class BaseGateway(
|
|
118
99
|
Path, bytes or URL used by the component as an avatar.
|
119
100
|
"""
|
120
101
|
|
121
|
-
REGISTRATION_FIELDS:
|
102
|
+
REGISTRATION_FIELDS: Sequence[FormField] = [
|
122
103
|
FormField(var="username", label="User name", required=True),
|
123
104
|
FormField(var="password", label="Password", required=True, private=True),
|
124
105
|
]
|
@@ -315,7 +296,7 @@ class BaseGateway(
|
|
315
296
|
self.session_cls: BaseSession = BaseSession.get_unique_subclass()
|
316
297
|
self.session_cls.xmpp = self
|
317
298
|
|
318
|
-
from
|
299
|
+
from ..group.room import LegacyMUC
|
319
300
|
|
320
301
|
LegacyMUC.get_self_or_unique_subclass().xmpp = self
|
321
302
|
|
@@ -328,6 +309,7 @@ class BaseGateway(
|
|
328
309
|
|
329
310
|
self.register_plugins()
|
330
311
|
self.__register_slixmpp_events()
|
312
|
+
self.__register_slixmpp_api()
|
331
313
|
self.roster.set_backend(RosterBackend(self))
|
332
314
|
|
333
315
|
self.register_plugin("pubsub", {"component_name": self.COMPONENT_NAME})
|
@@ -355,21 +337,11 @@ class BaseGateway(
|
|
355
337
|
# why does mypy need these type annotations? no idea
|
356
338
|
self.__adhoc_handler: AdhocProvider = AdhocProvider(self)
|
357
339
|
self.__chat_commands_handler: ChatCommandProvider = ChatCommandProvider(self)
|
358
|
-
|
359
|
-
|
360
|
-
self.__ping_handler = Ping(self)
|
361
|
-
self.__mam_handler = Mam(self)
|
362
|
-
self.__search_handler = Search(self)
|
363
|
-
self.__caps_handler = Caps(self)
|
364
|
-
self.__vcard_temp_handler = VCardTemp(self)
|
365
|
-
self.__muc_admin_handler = MucAdmin(self)
|
366
|
-
self.__registration = Registration(self)
|
340
|
+
|
367
341
|
self.__dispatcher = SessionDispatcher(self)
|
368
342
|
|
369
343
|
self.__register_commands()
|
370
344
|
|
371
|
-
self.__mam_cleanup_task = self.loop.create_task(self.__mam_cleanup())
|
372
|
-
|
373
345
|
MessageMixin.__init__(self) # ComponentXMPP does not call super().__init__()
|
374
346
|
|
375
347
|
async def __set_http(self):
|
@@ -378,13 +350,6 @@ class BaseGateway(
|
|
378
350
|
return
|
379
351
|
avatar_cache.http = self.http
|
380
352
|
|
381
|
-
async def __mam_cleanup(self):
|
382
|
-
if not config.MAM_MAX_DAYS:
|
383
|
-
return
|
384
|
-
while True:
|
385
|
-
await asyncio.sleep(3600 * 6)
|
386
|
-
self.store.mam.nuke_older_than(config.MAM_MAX_DAYS)
|
387
|
-
|
388
353
|
def __register_commands(self):
|
389
354
|
for cls in Command.subclasses:
|
390
355
|
if any(x is NotImplemented for x in [cls.CHAT_COMMAND, cls.NODE, cls.NAME]):
|
@@ -421,40 +386,27 @@ class BaseGateway(
|
|
421
386
|
loop.stop()
|
422
387
|
|
423
388
|
def __register_slixmpp_events(self):
|
389
|
+
self.del_event_handler("presence_subscribe", self._handle_subscribe)
|
390
|
+
self.del_event_handler("presence_unsubscribe", self._handle_unsubscribe)
|
391
|
+
self.del_event_handler("presence_subscribed", self._handle_subscribed)
|
392
|
+
self.del_event_handler("presence_unsubscribed", self._handle_unsubscribed)
|
393
|
+
self.del_event_handler(
|
394
|
+
"roster_subscription_request", self._handle_new_subscription
|
395
|
+
)
|
396
|
+
self.del_event_handler("presence_probe", self._handle_probe)
|
424
397
|
self.add_event_handler("session_start", self.__on_session_start)
|
425
398
|
self.add_event_handler("disconnected", self.connect)
|
426
|
-
|
427
|
-
|
428
|
-
self.
|
399
|
+
|
400
|
+
def __register_slixmpp_api(self) -> None:
|
401
|
+
self.plugin["xep_0231"].api.register(self.store.bob.get_bob, "get_bob")
|
402
|
+
self.plugin["xep_0231"].api.register(self.store.bob.set_bob, "set_bob")
|
403
|
+
self.plugin["xep_0231"].api.register(self.store.bob.del_bob, "del_bob")
|
429
404
|
|
430
405
|
@property # type: ignore
|
431
406
|
def jid(self):
|
432
407
|
# Override to avoid slixmpp deprecation warnings.
|
433
408
|
return self.boundjid
|
434
409
|
|
435
|
-
async def __on_group_chat_error(self, msg: Message):
|
436
|
-
condition = msg["error"].get_condition()
|
437
|
-
if condition not in KICKABLE_ERRORS:
|
438
|
-
return
|
439
|
-
|
440
|
-
try:
|
441
|
-
muc = await self.get_muc_from_stanza(msg)
|
442
|
-
except XMPPError as e:
|
443
|
-
log.debug("Not removing resource", exc_info=e)
|
444
|
-
return
|
445
|
-
mfrom = msg.get_from()
|
446
|
-
resource = mfrom.resource
|
447
|
-
try:
|
448
|
-
muc.remove_user_resource(resource)
|
449
|
-
except KeyError:
|
450
|
-
# this actually happens quite frequently on for both beagle and monal
|
451
|
-
# (not sure why?), but is of no consequence
|
452
|
-
log.debug("%s was not in the resources of %s", resource, muc)
|
453
|
-
else:
|
454
|
-
log.info(
|
455
|
-
"Removed %s from the resources of %s because of error", resource, muc
|
456
|
-
)
|
457
|
-
|
458
410
|
async def __on_session_start(self, event):
|
459
411
|
log.debug("Gateway session start: %s", event)
|
460
412
|
|
@@ -490,7 +442,7 @@ class BaseGateway(
|
|
490
442
|
pto=user.jid.bare, ptype="probe"
|
491
443
|
) # ensure we get all resources for user
|
492
444
|
session = self.session_cls.from_user(user)
|
493
|
-
session.create_task(self.
|
445
|
+
session.create_task(self.login_wrap(session))
|
494
446
|
if cached_avatar is not None:
|
495
447
|
await self.pubsub.broadcast_avatar(
|
496
448
|
self.boundjid.bare, session.user_jid, cached_avatar
|
@@ -551,7 +503,7 @@ class BaseGateway(
|
|
551
503
|
)
|
552
504
|
|
553
505
|
@timeit
|
554
|
-
async def
|
506
|
+
async def login_wrap(self, session: "BaseSession"):
|
555
507
|
session.send_gateway_status("Logging in…", show="dnd")
|
556
508
|
try:
|
557
509
|
status = await session.login()
|
@@ -612,21 +564,6 @@ class BaseGateway(
|
|
612
564
|
stanza.send()
|
613
565
|
return stanza
|
614
566
|
|
615
|
-
async def _on_user_register(self, iq: Iq):
|
616
|
-
session = self.get_session_from_stanza(iq)
|
617
|
-
for jid in config.ADMINS:
|
618
|
-
self.send_message(
|
619
|
-
mto=jid,
|
620
|
-
mbody=f"{iq.get_from()} has registered",
|
621
|
-
mtype="chat",
|
622
|
-
mfrom=self.boundjid.bare,
|
623
|
-
)
|
624
|
-
session.send_gateway_message(self.WELCOME_MESSAGE)
|
625
|
-
await self.__login_wrap(session)
|
626
|
-
|
627
|
-
async def _on_user_unregister(self, iq: Iq):
|
628
|
-
await self.session_cls.kill_by_jid(iq.get_from())
|
629
|
-
|
630
567
|
def raise_if_not_allowed_jid(self, jid: JID):
|
631
568
|
if not self.jid_validator.match(jid.bare):
|
632
569
|
raise XMPPError(
|
@@ -665,26 +602,6 @@ class BaseGateway(
|
|
665
602
|
except XMPPError:
|
666
603
|
pass
|
667
604
|
|
668
|
-
async def get_muc_from_stanza(self, iq: Union[Iq, Message]) -> "LegacyMUC":
|
669
|
-
ito = iq.get_to()
|
670
|
-
|
671
|
-
if ito == self.boundjid.bare:
|
672
|
-
raise XMPPError(
|
673
|
-
text="No MAM on the component itself, use a JID with a resource"
|
674
|
-
)
|
675
|
-
|
676
|
-
ifrom = iq.get_from()
|
677
|
-
user = self.store.users.get(ifrom)
|
678
|
-
if user is None:
|
679
|
-
raise XMPPError("registration-required")
|
680
|
-
|
681
|
-
session = self.get_session_from_user(user)
|
682
|
-
session.raise_if_not_logged()
|
683
|
-
|
684
|
-
muc = await session.bookmarks.by_jid(ito)
|
685
|
-
|
686
|
-
return muc
|
687
|
-
|
688
605
|
def exception(self, exception: Exception):
|
689
606
|
# """
|
690
607
|
# Called when a task created by slixmpp's internal (eg, on slix events) raises an Exception.
|
@@ -719,7 +636,7 @@ class BaseGateway(
|
|
719
636
|
async def w():
|
720
637
|
session.cancel_all_tasks()
|
721
638
|
await session.logout()
|
722
|
-
await self.
|
639
|
+
await self.login_wrap(session)
|
723
640
|
|
724
641
|
session.create_task(w())
|
725
642
|
|
@@ -949,20 +866,6 @@ class BaseGateway(
|
|
949
866
|
return tasks
|
950
867
|
|
951
868
|
|
952
|
-
KICKABLE_ERRORS = {
|
953
|
-
"gone",
|
954
|
-
"internal-server-error",
|
955
|
-
"item-not-found",
|
956
|
-
"jid-malformed",
|
957
|
-
"recipient-unavailable",
|
958
|
-
"redirect",
|
959
|
-
"remote-server-not-found",
|
960
|
-
"remote-server-timeout",
|
961
|
-
"service-unavailable",
|
962
|
-
"malformed error",
|
963
|
-
}
|
964
|
-
|
965
|
-
|
966
869
|
SLIXMPP_PLUGINS = [
|
967
870
|
"link_preview", # https://wiki.soprani.ca/CheogramApp/LinkPreviews
|
968
871
|
"xep_0030", # Service discovery
|
@@ -972,6 +875,7 @@ SLIXMPP_PLUGINS = [
|
|
972
875
|
"xep_0055", # Jabber search
|
973
876
|
"xep_0059", # Result Set Management
|
974
877
|
"xep_0066", # Out of Band Data
|
878
|
+
"xep_0071", # XHTML-IM (for stickers and custom emojis maybe later)
|
975
879
|
"xep_0077", # In-band registration
|
976
880
|
"xep_0084", # User Avatar
|
977
881
|
"xep_0085", # Chat state notifications
|
@@ -984,6 +888,7 @@ SLIXMPP_PLUGINS = [
|
|
984
888
|
"xep_0184", # Message Delivery Receipts
|
985
889
|
"xep_0199", # XMPP Ping
|
986
890
|
"xep_0221", # Data Forms Media Element
|
891
|
+
"xep_0231", # Bits of Binary (for stickers and custom emojis maybe later)
|
987
892
|
"xep_0249", # Direct MUC Invitations
|
988
893
|
"xep_0264", # Jingle Content Thumbnails
|
989
894
|
"xep_0280", # Carbons
|