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
slidge/__version__.py
CHANGED
slidge/command/adhoc.py
CHANGED
@@ -13,7 +13,7 @@ from . import Command, CommandResponseType, Confirmation, Form, TableResult
|
|
13
13
|
from .base import FormField
|
14
14
|
|
15
15
|
if TYPE_CHECKING:
|
16
|
-
from ..core.gateway
|
16
|
+
from ..core.gateway import BaseGateway
|
17
17
|
from ..core.session import BaseSession
|
18
18
|
|
19
19
|
|
slidge/command/base.py
CHANGED
@@ -6,9 +6,9 @@ from typing import (
|
|
6
6
|
Any,
|
7
7
|
Awaitable,
|
8
8
|
Callable,
|
9
|
-
Collection,
|
10
9
|
Iterable,
|
11
10
|
Optional,
|
11
|
+
Sequence,
|
12
12
|
Type,
|
13
13
|
TypedDict,
|
14
14
|
Union,
|
@@ -54,11 +54,11 @@ class TableResult:
|
|
54
54
|
Structured data as the result of a command
|
55
55
|
"""
|
56
56
|
|
57
|
-
fields:
|
57
|
+
fields: Sequence["FormField"]
|
58
58
|
"""
|
59
59
|
The 'columns names' of the table.
|
60
60
|
"""
|
61
|
-
items:
|
61
|
+
items: Sequence[dict[str, Union[str, JID]]]
|
62
62
|
"""
|
63
63
|
The rows of the table. Each row is a dict where keys are the fields ``var``
|
64
64
|
attribute.
|
@@ -149,7 +149,7 @@ class Form:
|
|
149
149
|
|
150
150
|
title: str
|
151
151
|
instructions: str
|
152
|
-
fields:
|
152
|
+
fields: Sequence["FormField"]
|
153
153
|
handler: FormHandlerType
|
154
154
|
handler_args: Iterable[Any] = field(default_factory=list)
|
155
155
|
handler_kwargs: dict[str, Any] = field(default_factory=dict)
|
slidge/contact/contact.py
CHANGED
@@ -282,8 +282,9 @@ class LegacyContact(
|
|
282
282
|
self._privileged_send(stanza)
|
283
283
|
return stanza # type:ignore
|
284
284
|
|
285
|
-
if
|
286
|
-
self.
|
285
|
+
if isinstance(stanza, Presence):
|
286
|
+
if not self._updating_info:
|
287
|
+
self.__propagate_to_participants(stanza)
|
287
288
|
if (
|
288
289
|
not self.is_friend
|
289
290
|
and stanza["type"] not in self._NON_FRIEND_PRESENCES_FILTER
|
slidge/contact/roster.py
CHANGED
@@ -92,6 +92,13 @@ class LegacyRoster(
|
|
92
92
|
stored = self.__store.get_by_jid(self.session.user_pk, contact_jid)
|
93
93
|
return await self.__update_contact(stored, legacy_id, username)
|
94
94
|
|
95
|
+
def by_jid_only_if_exists(self, contact_jid: JID) -> LegacyContactType | None:
|
96
|
+
with self.__store.session():
|
97
|
+
stored = self.__store.get_by_jid(self.session.user_pk, contact_jid)
|
98
|
+
if stored is not None and stored.updated:
|
99
|
+
return self._contact_cls.from_store(self.session, stored)
|
100
|
+
return None
|
101
|
+
|
95
102
|
async def by_legacy_id(
|
96
103
|
self, legacy_id: LegacyUserIdType, *args, **kwargs
|
97
104
|
) -> LegacyContactType:
|
@@ -5,17 +5,19 @@ from slixmpp import Presence
|
|
5
5
|
from slixmpp.exceptions import XMPPError
|
6
6
|
from slixmpp.xmlstream import StanzaBase
|
7
7
|
|
8
|
+
from .util import DispatcherMixin
|
9
|
+
|
8
10
|
if TYPE_CHECKING:
|
9
|
-
from .
|
11
|
+
from slidge.core.gateway import BaseGateway
|
10
12
|
|
11
13
|
|
12
|
-
class
|
14
|
+
class CapsMixin(DispatcherMixin):
|
13
15
|
def __init__(self, xmpp: "BaseGateway"):
|
14
|
-
|
16
|
+
super().__init__(xmpp)
|
15
17
|
xmpp.del_filter("out", xmpp.plugin["xep_0115"]._filter_add_caps)
|
16
18
|
xmpp.add_filter("out", self._filter_add_caps) # type:ignore
|
17
19
|
|
18
|
-
async def _filter_add_caps(self, stanza: StanzaBase):
|
20
|
+
async def _filter_add_caps(self, stanza: StanzaBase) -> StanzaBase:
|
19
21
|
# we rolled our own "add caps on presences" filter because
|
20
22
|
# there is too much magic happening in slixmpp
|
21
23
|
# anyway, we probably want to roll our own "dynamic disco"/caps
|
@@ -5,13 +5,15 @@ from slixmpp.exceptions import XMPPError
|
|
5
5
|
from slixmpp.plugins.xep_0030.stanza.items import DiscoItems
|
6
6
|
from slixmpp.types import OptJid
|
7
7
|
|
8
|
+
from .util import DispatcherMixin
|
9
|
+
|
8
10
|
if TYPE_CHECKING:
|
9
|
-
from .
|
11
|
+
from slidge.core.gateway import BaseGateway
|
10
12
|
|
11
13
|
|
12
|
-
class
|
14
|
+
class DiscoMixin(DispatcherMixin):
|
13
15
|
def __init__(self, xmpp: "BaseGateway"):
|
14
|
-
|
16
|
+
super().__init__(xmpp)
|
15
17
|
|
16
18
|
xmpp.plugin["xep_0030"].set_node_handler(
|
17
19
|
"get_info",
|
@@ -36,15 +38,11 @@ class Disco:
|
|
36
38
|
if ifrom is None:
|
37
39
|
raise XMPPError("subscription-required")
|
38
40
|
|
39
|
-
|
40
|
-
|
41
|
-
raise XMPPError("registration-required")
|
42
|
-
session = self.xmpp.get_session_from_user(user)
|
43
|
-
await session.wait_for_ready()
|
41
|
+
assert jid is not None
|
42
|
+
session = await self._get_session_from_jid(jid=ifrom)
|
44
43
|
|
45
44
|
log.debug("Looking for entity: %s", jid)
|
46
45
|
|
47
|
-
assert jid is not None
|
48
46
|
entity = await session.get_contact_or_group_or_participant(jid)
|
49
47
|
|
50
48
|
if entity is None:
|
@@ -61,16 +59,12 @@ class Disco:
|
|
61
59
|
if jid != self.xmpp.boundjid.bare:
|
62
60
|
return DiscoItems()
|
63
61
|
|
64
|
-
|
65
|
-
|
66
|
-
raise XMPPError("registration-required")
|
67
|
-
|
68
|
-
session = self.xmpp.get_session_from_user(user)
|
69
|
-
await session.wait_for_ready()
|
62
|
+
assert ifrom is not None
|
63
|
+
session = await self._get_session_from_jid(ifrom)
|
70
64
|
|
71
65
|
d = DiscoItems()
|
72
|
-
for
|
73
|
-
d.add_item(
|
66
|
+
for room in self.xmpp.store.rooms.get_all_jid_and_names(session.user_pk):
|
67
|
+
d.add_item(room.jid, name=room.name)
|
74
68
|
|
75
69
|
return d
|
76
70
|
|
@@ -0,0 +1,40 @@
|
|
1
|
+
from slixmpp import Message
|
2
|
+
from slixmpp.xmlstream import StanzaBase
|
3
|
+
|
4
|
+
from ..util import DispatcherMixin, exceptions_to_xmpp_errors
|
5
|
+
|
6
|
+
|
7
|
+
class ChatStateMixin(DispatcherMixin):
|
8
|
+
def __init__(self, xmpp) -> None:
|
9
|
+
super().__init__(xmpp)
|
10
|
+
xmpp.add_event_handler("chatstate_active", self.on_chatstate_active)
|
11
|
+
xmpp.add_event_handler("chatstate_inactive", self.on_chatstate_inactive)
|
12
|
+
xmpp.add_event_handler("chatstate_composing", self.on_chatstate_composing)
|
13
|
+
xmpp.add_event_handler("chatstate_paused", self.on_chatstate_paused)
|
14
|
+
|
15
|
+
@exceptions_to_xmpp_errors
|
16
|
+
async def on_chatstate_active(self, msg: StanzaBase) -> None:
|
17
|
+
assert isinstance(msg, Message)
|
18
|
+
if msg["body"]:
|
19
|
+
# if there is a body, it's handled in on_legacy_message()
|
20
|
+
return
|
21
|
+
session, entity, thread = await self._get_session_entity_thread(msg)
|
22
|
+
await session.on_active(entity, thread)
|
23
|
+
|
24
|
+
@exceptions_to_xmpp_errors
|
25
|
+
async def on_chatstate_inactive(self, msg: StanzaBase) -> None:
|
26
|
+
assert isinstance(msg, Message)
|
27
|
+
session, entity, thread = await self._get_session_entity_thread(msg)
|
28
|
+
await session.on_inactive(entity, thread)
|
29
|
+
|
30
|
+
@exceptions_to_xmpp_errors
|
31
|
+
async def on_chatstate_composing(self, msg: StanzaBase) -> None:
|
32
|
+
assert isinstance(msg, Message)
|
33
|
+
session, entity, thread = await self._get_session_entity_thread(msg)
|
34
|
+
await session.on_composing(entity, thread)
|
35
|
+
|
36
|
+
@exceptions_to_xmpp_errors
|
37
|
+
async def on_chatstate_paused(self, msg: StanzaBase) -> None:
|
38
|
+
assert isinstance(msg, Message)
|
39
|
+
session, entity, thread = await self._get_session_entity_thread(msg)
|
40
|
+
await session.on_paused(entity, thread)
|
@@ -0,0 +1,67 @@
|
|
1
|
+
from slixmpp import JID, Message
|
2
|
+
from slixmpp.xmlstream import StanzaBase
|
3
|
+
|
4
|
+
from ....group.room import LegacyMUC
|
5
|
+
from ....util.types import Recipient
|
6
|
+
from ..util import (
|
7
|
+
DispatcherMixin,
|
8
|
+
_get_entity,
|
9
|
+
_xmpp_to_legacy_thread,
|
10
|
+
exceptions_to_xmpp_errors,
|
11
|
+
)
|
12
|
+
|
13
|
+
|
14
|
+
class MarkerMixin(DispatcherMixin):
|
15
|
+
def __init__(self, xmpp) -> None:
|
16
|
+
super().__init__(xmpp)
|
17
|
+
xmpp.add_event_handler("marker_displayed", self.on_marker_displayed)
|
18
|
+
xmpp.add_event_handler(
|
19
|
+
"message_displayed_synchronization_publish",
|
20
|
+
self.on_message_displayed_synchronization_publish,
|
21
|
+
)
|
22
|
+
|
23
|
+
@exceptions_to_xmpp_errors
|
24
|
+
async def on_marker_displayed(self, msg: StanzaBase) -> None:
|
25
|
+
assert isinstance(msg, Message)
|
26
|
+
session = await self._get_session(msg)
|
27
|
+
|
28
|
+
e: Recipient = await _get_entity(session, msg)
|
29
|
+
legacy_thread = await _xmpp_to_legacy_thread(session, msg, e)
|
30
|
+
displayed_msg_id = msg["displayed"]["id"]
|
31
|
+
if not isinstance(e, LegacyMUC) and self.xmpp.MARK_ALL_MESSAGES:
|
32
|
+
to_mark = e.get_msg_xmpp_id_up_to(displayed_msg_id) # type: ignore
|
33
|
+
if to_mark is None:
|
34
|
+
session.log.debug("Can't mark all messages up to %s", displayed_msg_id)
|
35
|
+
to_mark = [displayed_msg_id]
|
36
|
+
else:
|
37
|
+
to_mark = [displayed_msg_id]
|
38
|
+
for xmpp_id in to_mark:
|
39
|
+
await session.on_displayed(
|
40
|
+
e, self._xmpp_msg_id_to_legacy(session, xmpp_id), legacy_thread
|
41
|
+
)
|
42
|
+
if isinstance(e, LegacyMUC):
|
43
|
+
await e.echo(msg, None)
|
44
|
+
|
45
|
+
@exceptions_to_xmpp_errors
|
46
|
+
async def on_message_displayed_synchronization_publish(
|
47
|
+
self, msg: StanzaBase
|
48
|
+
) -> None:
|
49
|
+
assert isinstance(msg, Message)
|
50
|
+
chat_jid = JID(msg["pubsub_event"]["items"]["item"]["id"])
|
51
|
+
if chat_jid.server != self.xmpp.boundjid.bare:
|
52
|
+
return
|
53
|
+
|
54
|
+
session = await self._get_session(msg, timeout=None)
|
55
|
+
|
56
|
+
if chat_jid == self.xmpp.boundjid.bare:
|
57
|
+
return
|
58
|
+
|
59
|
+
chat = await session.get_contact_or_group_or_participant(chat_jid)
|
60
|
+
if not isinstance(chat, LegacyMUC):
|
61
|
+
session.log.debug("Ignoring non-groupchat MDS event")
|
62
|
+
return
|
63
|
+
|
64
|
+
stanza_id = msg["pubsub_event"]["items"]["item"]["displayed"]["stanza_id"]["id"]
|
65
|
+
await session.on_displayed(
|
66
|
+
chat, self._xmpp_msg_id_to_legacy(session, stanza_id)
|
67
|
+
)
|
@@ -0,0 +1,397 @@
|
|
1
|
+
import logging
|
2
|
+
from copy import copy
|
3
|
+
from xml.etree import ElementTree
|
4
|
+
|
5
|
+
from slixmpp import JID, Message
|
6
|
+
from slixmpp.exceptions import XMPPError
|
7
|
+
|
8
|
+
from ....contact.contact import LegacyContact
|
9
|
+
from ....group.participant import LegacyParticipant
|
10
|
+
from ....group.room import LegacyMUC
|
11
|
+
from ....util.types import LinkPreview, Recipient
|
12
|
+
from ....util.util import dict_to_named_tuple, remove_emoji_variation_selector_16
|
13
|
+
from ... import config
|
14
|
+
from ...session import BaseSession
|
15
|
+
from ..util import DispatcherMixin, exceptions_to_xmpp_errors
|
16
|
+
|
17
|
+
|
18
|
+
class MessageContentMixin(DispatcherMixin):
|
19
|
+
def __init__(self, xmpp):
|
20
|
+
super().__init__(xmpp)
|
21
|
+
xmpp.add_event_handler("legacy_message", self.on_legacy_message)
|
22
|
+
xmpp.add_event_handler("message_correction", self.on_message_correction)
|
23
|
+
xmpp.add_event_handler("message_retract", self.on_message_retract)
|
24
|
+
xmpp.add_event_handler("groupchat_message", self.on_groupchat_message)
|
25
|
+
xmpp.add_event_handler("reactions", self.on_reactions)
|
26
|
+
|
27
|
+
async def on_groupchat_message(self, msg: Message) -> None:
|
28
|
+
await self.on_legacy_message(msg)
|
29
|
+
|
30
|
+
@exceptions_to_xmpp_errors
|
31
|
+
async def on_legacy_message(self, msg: Message):
|
32
|
+
"""
|
33
|
+
Meant to be called from :class:`BaseGateway` only.
|
34
|
+
|
35
|
+
:param msg:
|
36
|
+
:return:
|
37
|
+
"""
|
38
|
+
# we MUST not use `if m["replace"]["id"]` because it adds the tag if not
|
39
|
+
# present. this is a problem for MUC echoed messages
|
40
|
+
if msg.get_plugin("replace", check=True) is not None:
|
41
|
+
# ignore last message correction (handled by a specific method)
|
42
|
+
return
|
43
|
+
if msg.get_plugin("apply_to", check=True) is not None:
|
44
|
+
# ignore message retraction (handled by a specific method)
|
45
|
+
return
|
46
|
+
if msg.get_plugin("reactions", check=True) is not None:
|
47
|
+
# ignore message reaction fallback.
|
48
|
+
# the reaction itself is handled by self.react_from_msg().
|
49
|
+
return
|
50
|
+
if msg.get_plugin("retract", check=True) is not None:
|
51
|
+
# ignore message retraction fallback.
|
52
|
+
# the retraction itself is handled by self.on_retract
|
53
|
+
return
|
54
|
+
cid = None
|
55
|
+
if msg.get_plugin("html", check=True) is not None:
|
56
|
+
body = ElementTree.fromstring("<body>" + msg["html"].get_body() + "</body>")
|
57
|
+
p = body.findall("p")
|
58
|
+
if p is not None and len(p) == 1:
|
59
|
+
if p[0].text is None or not p[0].text.strip():
|
60
|
+
images = p[0].findall("img")
|
61
|
+
if len(images) == 1:
|
62
|
+
# no text, single img ⇒ this is a sticker
|
63
|
+
# other cases should be interpreted as "custom emojis" in text
|
64
|
+
src = images[0].get("src")
|
65
|
+
if src is not None and src.startswith("cid:"):
|
66
|
+
cid = src.removeprefix("cid:")
|
67
|
+
|
68
|
+
session, entity, thread = await self._get_session_entity_thread(msg)
|
69
|
+
|
70
|
+
if msg.get_plugin("oob", check=True) is not None:
|
71
|
+
url = msg["oob"]["url"]
|
72
|
+
else:
|
73
|
+
url = None
|
74
|
+
|
75
|
+
if msg.get_plugin("reply", check=True):
|
76
|
+
text, reply_to_msg_id, reply_to, reply_fallback = await self.__get_reply(
|
77
|
+
msg, session, entity
|
78
|
+
)
|
79
|
+
else:
|
80
|
+
text = msg["body"]
|
81
|
+
reply_to_msg_id = None
|
82
|
+
reply_to = None
|
83
|
+
reply_fallback = None
|
84
|
+
|
85
|
+
if msg.get_plugin("link_previews", check=True):
|
86
|
+
link_previews = [
|
87
|
+
dict_to_named_tuple(p, LinkPreview) for p in msg["link_previews"]
|
88
|
+
]
|
89
|
+
else:
|
90
|
+
link_previews = []
|
91
|
+
|
92
|
+
if url:
|
93
|
+
legacy_msg_id = await self.__send_url(
|
94
|
+
url,
|
95
|
+
session,
|
96
|
+
entity,
|
97
|
+
reply_to_msg_id=reply_to_msg_id,
|
98
|
+
reply_to_fallback_text=reply_fallback,
|
99
|
+
reply_to=reply_to,
|
100
|
+
thread=thread,
|
101
|
+
)
|
102
|
+
elif cid:
|
103
|
+
legacy_msg_id = await self.__send_bob(
|
104
|
+
msg.get_from(),
|
105
|
+
cid,
|
106
|
+
session,
|
107
|
+
entity,
|
108
|
+
reply_to_msg_id=reply_to_msg_id,
|
109
|
+
reply_to_fallback_text=reply_fallback,
|
110
|
+
reply_to=reply_to,
|
111
|
+
thread=thread,
|
112
|
+
)
|
113
|
+
elif text:
|
114
|
+
if isinstance(entity, LegacyMUC):
|
115
|
+
mentions = {"mentions": await entity.parse_mentions(text)}
|
116
|
+
else:
|
117
|
+
mentions = {}
|
118
|
+
legacy_msg_id = await session.on_text(
|
119
|
+
entity,
|
120
|
+
text,
|
121
|
+
reply_to_msg_id=reply_to_msg_id,
|
122
|
+
reply_to_fallback_text=reply_fallback,
|
123
|
+
reply_to=reply_to,
|
124
|
+
thread=thread,
|
125
|
+
link_previews=link_previews,
|
126
|
+
**mentions,
|
127
|
+
)
|
128
|
+
else:
|
129
|
+
log.debug("Ignoring %s", msg.get_id())
|
130
|
+
return
|
131
|
+
|
132
|
+
if isinstance(entity, LegacyMUC):
|
133
|
+
await entity.echo(msg, legacy_msg_id)
|
134
|
+
if legacy_msg_id is not None:
|
135
|
+
self.xmpp.store.sent.set_group_message(
|
136
|
+
session.user_pk, str(legacy_msg_id), msg.get_id()
|
137
|
+
)
|
138
|
+
else:
|
139
|
+
self.__ack(msg)
|
140
|
+
if legacy_msg_id is not None:
|
141
|
+
self.xmpp.store.sent.set_message(
|
142
|
+
session.user_pk, str(legacy_msg_id), msg.get_id()
|
143
|
+
)
|
144
|
+
if session.MESSAGE_IDS_ARE_THREAD_IDS and (t := msg["thread"]):
|
145
|
+
self.xmpp.store.sent.set_thread(
|
146
|
+
session.user_pk, t, str(legacy_msg_id)
|
147
|
+
)
|
148
|
+
|
149
|
+
@exceptions_to_xmpp_errors
|
150
|
+
async def on_message_correction(self, msg: Message):
|
151
|
+
if msg.get_plugin("retract", check=True) is not None:
|
152
|
+
# ignore message retraction fallback (fallback=last msg correction)
|
153
|
+
return
|
154
|
+
session, entity, thread = await self._get_session_entity_thread(msg)
|
155
|
+
xmpp_id = msg["replace"]["id"]
|
156
|
+
if isinstance(entity, LegacyMUC):
|
157
|
+
legacy_id_str = self.xmpp.store.sent.get_group_legacy_id(
|
158
|
+
session.user_pk, xmpp_id
|
159
|
+
)
|
160
|
+
if legacy_id_str is None:
|
161
|
+
legacy_id = self._xmpp_msg_id_to_legacy(session, xmpp_id)
|
162
|
+
else:
|
163
|
+
legacy_id = self.xmpp.LEGACY_MSG_ID_TYPE(legacy_id_str)
|
164
|
+
else:
|
165
|
+
legacy_id = self._xmpp_msg_id_to_legacy(session, xmpp_id)
|
166
|
+
|
167
|
+
if isinstance(entity, LegacyMUC):
|
168
|
+
mentions = await entity.parse_mentions(msg["body"])
|
169
|
+
else:
|
170
|
+
mentions = None
|
171
|
+
|
172
|
+
if previews := msg["link_previews"]:
|
173
|
+
link_previews = [dict_to_named_tuple(p, LinkPreview) for p in previews]
|
174
|
+
else:
|
175
|
+
link_previews = []
|
176
|
+
|
177
|
+
if legacy_id is None:
|
178
|
+
log.debug("Did not find legacy ID to correct")
|
179
|
+
new_legacy_msg_id = await session.on_text(
|
180
|
+
entity,
|
181
|
+
"Correction:" + msg["body"],
|
182
|
+
thread=thread,
|
183
|
+
mentions=mentions,
|
184
|
+
link_previews=link_previews,
|
185
|
+
)
|
186
|
+
elif (
|
187
|
+
not msg["body"].strip()
|
188
|
+
and config.CORRECTION_EMPTY_BODY_AS_RETRACTION
|
189
|
+
and entity.RETRACTION
|
190
|
+
):
|
191
|
+
await session.on_retract(entity, legacy_id, thread=thread)
|
192
|
+
new_legacy_msg_id = None
|
193
|
+
elif entity.CORRECTION:
|
194
|
+
new_legacy_msg_id = await session.on_correct(
|
195
|
+
entity,
|
196
|
+
msg["body"],
|
197
|
+
legacy_id,
|
198
|
+
thread=thread,
|
199
|
+
mentions=mentions,
|
200
|
+
link_previews=link_previews,
|
201
|
+
)
|
202
|
+
else:
|
203
|
+
session.send_gateway_message(
|
204
|
+
"Last message correction is not supported by this legacy service. "
|
205
|
+
"Slidge will send your correction as new message."
|
206
|
+
)
|
207
|
+
if (
|
208
|
+
config.LAST_MESSAGE_CORRECTION_RETRACTION_WORKAROUND
|
209
|
+
and entity.RETRACTION
|
210
|
+
and legacy_id is not None
|
211
|
+
):
|
212
|
+
if legacy_id is not None:
|
213
|
+
session.send_gateway_message(
|
214
|
+
"Slidge will attempt to retract the original message you wanted"
|
215
|
+
" to edit."
|
216
|
+
)
|
217
|
+
await session.on_retract(entity, legacy_id, thread=thread)
|
218
|
+
|
219
|
+
new_legacy_msg_id = await session.on_text(
|
220
|
+
entity,
|
221
|
+
"Correction: " + msg["body"],
|
222
|
+
thread=thread,
|
223
|
+
mentions=mentions,
|
224
|
+
link_previews=link_previews,
|
225
|
+
)
|
226
|
+
|
227
|
+
if isinstance(entity, LegacyMUC):
|
228
|
+
if new_legacy_msg_id is not None:
|
229
|
+
self.xmpp.store.sent.set_group_message(
|
230
|
+
session.user_pk, new_legacy_msg_id, msg.get_id()
|
231
|
+
)
|
232
|
+
await entity.echo(msg, new_legacy_msg_id)
|
233
|
+
else:
|
234
|
+
self.__ack(msg)
|
235
|
+
if new_legacy_msg_id is not None:
|
236
|
+
self.xmpp.store.sent.set_message(
|
237
|
+
session.user_pk, new_legacy_msg_id, msg.get_id()
|
238
|
+
)
|
239
|
+
|
240
|
+
@exceptions_to_xmpp_errors
|
241
|
+
async def on_message_retract(self, msg: Message):
|
242
|
+
session, entity, thread = await self._get_session_entity_thread(msg)
|
243
|
+
if not entity.RETRACTION:
|
244
|
+
raise XMPPError(
|
245
|
+
"bad-request",
|
246
|
+
"This legacy service does not support message retraction.",
|
247
|
+
)
|
248
|
+
xmpp_id: str = msg["retract"]["id"]
|
249
|
+
legacy_id = self._xmpp_msg_id_to_legacy(session, xmpp_id)
|
250
|
+
if legacy_id:
|
251
|
+
await session.on_retract(entity, legacy_id, thread=thread)
|
252
|
+
if isinstance(entity, LegacyMUC):
|
253
|
+
await entity.echo(msg, None)
|
254
|
+
else:
|
255
|
+
log.debug("Ignored retraction from user")
|
256
|
+
self.__ack(msg)
|
257
|
+
|
258
|
+
@exceptions_to_xmpp_errors
|
259
|
+
async def on_reactions(self, msg: Message):
|
260
|
+
session, entity, thread = await self._get_session_entity_thread(msg)
|
261
|
+
react_to: str = msg["reactions"]["id"]
|
262
|
+
|
263
|
+
special_msg = session.SPECIAL_MSG_ID_PREFIX and react_to.startswith(
|
264
|
+
session.SPECIAL_MSG_ID_PREFIX
|
265
|
+
)
|
266
|
+
|
267
|
+
if special_msg:
|
268
|
+
legacy_id = react_to
|
269
|
+
else:
|
270
|
+
legacy_id = self._xmpp_msg_id_to_legacy(session, react_to)
|
271
|
+
|
272
|
+
if not legacy_id:
|
273
|
+
log.debug("Ignored reaction from user")
|
274
|
+
raise XMPPError(
|
275
|
+
"internal-server-error",
|
276
|
+
"Could not convert the XMPP msg ID to a legacy ID",
|
277
|
+
)
|
278
|
+
|
279
|
+
emojis = [
|
280
|
+
remove_emoji_variation_selector_16(r["value"]) for r in msg["reactions"]
|
281
|
+
]
|
282
|
+
error_msg = None
|
283
|
+
entity = entity
|
284
|
+
|
285
|
+
if not special_msg:
|
286
|
+
if entity.REACTIONS_SINGLE_EMOJI and len(emojis) > 1:
|
287
|
+
error_msg = "Maximum 1 emoji/message"
|
288
|
+
|
289
|
+
if not error_msg and (subset := await entity.available_emojis(legacy_id)):
|
290
|
+
if not set(emojis).issubset(subset):
|
291
|
+
error_msg = f"You can only react with the following emojis: {''.join(subset)}"
|
292
|
+
|
293
|
+
if error_msg:
|
294
|
+
session.send_gateway_message(error_msg)
|
295
|
+
if not isinstance(entity, LegacyMUC):
|
296
|
+
# no need to carbon for groups, we just don't echo the stanza
|
297
|
+
entity.react(legacy_id, carbon=True) # type: ignore
|
298
|
+
await session.on_react(entity, legacy_id, [], thread=thread)
|
299
|
+
raise XMPPError("not-acceptable", text=error_msg)
|
300
|
+
|
301
|
+
await session.on_react(entity, legacy_id, emojis, thread=thread)
|
302
|
+
if isinstance(entity, LegacyMUC):
|
303
|
+
await entity.echo(msg, None)
|
304
|
+
else:
|
305
|
+
self.__ack(msg)
|
306
|
+
|
307
|
+
multi = self.xmpp.store.multi.get_xmpp_ids(session.user_pk, react_to)
|
308
|
+
if not multi:
|
309
|
+
return
|
310
|
+
multi = [m for m in multi if react_to != m]
|
311
|
+
|
312
|
+
if isinstance(entity, LegacyMUC):
|
313
|
+
for xmpp_id in multi:
|
314
|
+
mc = copy(msg)
|
315
|
+
mc["reactions"]["id"] = xmpp_id
|
316
|
+
await entity.echo(mc)
|
317
|
+
elif isinstance(entity, LegacyContact):
|
318
|
+
for xmpp_id in multi:
|
319
|
+
entity.react(legacy_id, emojis, xmpp_id=xmpp_id, carbon=True)
|
320
|
+
|
321
|
+
def __ack(self, msg: Message):
|
322
|
+
if not self.xmpp.PROPER_RECEIPTS:
|
323
|
+
self.xmpp.delivery_receipt.ack(msg)
|
324
|
+
|
325
|
+
async def __get_reply(
|
326
|
+
self, msg: Message, session: BaseSession, entity: Recipient
|
327
|
+
) -> tuple[
|
328
|
+
str, str | int | None, LegacyContact | LegacyParticipant | None, str | None
|
329
|
+
]:
|
330
|
+
try:
|
331
|
+
reply_to_msg_id = self._xmpp_msg_id_to_legacy(session, msg["reply"]["id"])
|
332
|
+
except XMPPError:
|
333
|
+
session.log.debug(
|
334
|
+
"Could not determine reply-to legacy msg ID, sending quote instead."
|
335
|
+
)
|
336
|
+
return msg["body"], None, None, None
|
337
|
+
|
338
|
+
reply_to_jid = JID(msg["reply"]["to"])
|
339
|
+
reply_to = None
|
340
|
+
if msg["type"] == "chat":
|
341
|
+
if reply_to_jid.bare != session.user_jid.bare:
|
342
|
+
try:
|
343
|
+
reply_to = await session.contacts.by_jid(reply_to_jid)
|
344
|
+
except XMPPError:
|
345
|
+
pass
|
346
|
+
elif msg["type"] == "groupchat":
|
347
|
+
nick = reply_to_jid.resource
|
348
|
+
try:
|
349
|
+
muc = await session.bookmarks.by_jid(reply_to_jid)
|
350
|
+
except XMPPError:
|
351
|
+
pass
|
352
|
+
else:
|
353
|
+
if nick != muc.user_nick:
|
354
|
+
reply_to = await muc.get_participant(
|
355
|
+
reply_to_jid.resource, store=False
|
356
|
+
)
|
357
|
+
|
358
|
+
if msg.get_plugin("fallback", check=True) and (
|
359
|
+
isinstance(entity, LegacyMUC) or entity.REPLIES
|
360
|
+
):
|
361
|
+
text = msg["fallback"].get_stripped_body(self.xmpp["xep_0461"].namespace)
|
362
|
+
try:
|
363
|
+
reply_fallback = msg["reply"].get_fallback_body()
|
364
|
+
except AttributeError:
|
365
|
+
reply_fallback = None
|
366
|
+
else:
|
367
|
+
text = msg["body"]
|
368
|
+
reply_fallback = None
|
369
|
+
|
370
|
+
return text, reply_to_msg_id, reply_to, reply_fallback
|
371
|
+
|
372
|
+
async def __send_url(
|
373
|
+
self, url: str, session: BaseSession, entity: Recipient, **kwargs
|
374
|
+
) -> int | str | None:
|
375
|
+
async with self.xmpp.http.get(url) as response:
|
376
|
+
if response.status >= 400:
|
377
|
+
session.log.warning(
|
378
|
+
"OOB url cannot be downloaded: %s, sending the URL as text"
|
379
|
+
" instead.",
|
380
|
+
response,
|
381
|
+
)
|
382
|
+
return await session.on_text(entity, url, **kwargs)
|
383
|
+
|
384
|
+
return await session.on_file(entity, url, http_response=response, **kwargs)
|
385
|
+
|
386
|
+
async def __send_bob(
|
387
|
+
self, from_: JID, cid: str, session: BaseSession, entity: Recipient, **kwargs
|
388
|
+
) -> int | str | None:
|
389
|
+
sticker = self.xmpp.store.bob.get_sticker(cid)
|
390
|
+
if sticker is None:
|
391
|
+
await self.xmpp.plugin["xep_0231"].get_bob(from_, cid)
|
392
|
+
sticker = self.xmpp.store.bob.get_sticker(cid)
|
393
|
+
assert sticker is not None
|
394
|
+
return await session.on_sticker(entity, sticker, **kwargs)
|
395
|
+
|
396
|
+
|
397
|
+
log = logging.getLogger(__name__)
|