slidge 0.1.0b2__py3-none-any.whl → 0.1.1__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- slidge/__init__.py +55 -31
- slidge/__main__.py +118 -116
- slidge/command/__init__.py +28 -0
- slidge/command/adhoc.py +258 -0
- slidge/command/admin.py +193 -0
- slidge/command/base.py +441 -0
- slidge/command/categories.py +3 -0
- slidge/command/chat_command.py +288 -0
- slidge/command/register.py +179 -0
- slidge/command/user.py +250 -0
- slidge/contact/__init__.py +8 -0
- slidge/contact/contact.py +452 -0
- slidge/contact/roster.py +192 -0
- slidge/core/__init__.py +2 -0
- slidge/core/cache.py +183 -0
- slidge/core/config.py +216 -0
- slidge/core/gateway/__init__.py +3 -0
- slidge/core/gateway/base.py +895 -0
- slidge/core/gateway/caps.py +63 -0
- slidge/core/gateway/delivery_receipt.py +52 -0
- slidge/core/gateway/disco.py +80 -0
- slidge/core/gateway/mam.py +75 -0
- slidge/core/gateway/muc_admin.py +35 -0
- slidge/core/gateway/ping.py +66 -0
- slidge/core/gateway/presence.py +95 -0
- slidge/core/gateway/registration.py +53 -0
- slidge/core/gateway/search.py +102 -0
- slidge/core/gateway/session_dispatcher.py +789 -0
- slidge/core/gateway/vcard_temp.py +130 -0
- slidge/core/mixins/__init__.py +19 -0
- slidge/core/mixins/attachment.py +506 -0
- slidge/core/mixins/avatar.py +167 -0
- slidge/core/mixins/base.py +31 -0
- slidge/core/mixins/disco.py +130 -0
- slidge/core/mixins/lock.py +31 -0
- slidge/core/mixins/message.py +398 -0
- slidge/core/mixins/message_maker.py +154 -0
- slidge/core/mixins/presence.py +217 -0
- slidge/core/mixins/recipient.py +43 -0
- slidge/core/pubsub.py +282 -116
- slidge/core/session.py +595 -372
- slidge/group/__init__.py +10 -0
- slidge/group/archive.py +125 -0
- slidge/group/bookmarks.py +163 -0
- slidge/group/participant.py +458 -0
- slidge/group/room.py +1103 -0
- slidge/migration.py +18 -0
- slidge/slixfix/__init__.py +68 -0
- slidge/{util/xep_0084 → slixfix/link_preview}/__init__.py +3 -5
- slidge/slixfix/link_preview/link_preview.py +17 -0
- slidge/slixfix/link_preview/stanza.py +99 -0
- slidge/slixfix/roster.py +60 -0
- slidge/{util → slixfix}/xep_0077/register.py +14 -2
- slidge/slixfix/xep_0077/stanza.py +104 -0
- slidge/{util → slixfix}/xep_0100/gateway.py +25 -15
- slidge/slixfix/xep_0100/stanza.py +9 -0
- slidge/slixfix/xep_0153/__init__.py +10 -0
- slidge/slixfix/xep_0153/stanza.py +25 -0
- slidge/slixfix/xep_0153/vcard_avatar.py +23 -0
- slidge/slixfix/xep_0264/__init__.py +5 -0
- slidge/slixfix/xep_0264/stanza.py +36 -0
- slidge/slixfix/xep_0264/thumbnail.py +23 -0
- slidge/slixfix/xep_0292/__init__.py +5 -0
- slidge/slixfix/xep_0292/vcard4.py +100 -0
- slidge/slixfix/xep_0313/__init__.py +12 -0
- slidge/slixfix/xep_0313/mam.py +262 -0
- slidge/slixfix/xep_0313/stanza.py +359 -0
- slidge/slixfix/xep_0317/__init__.py +5 -0
- slidge/slixfix/xep_0317/hats.py +17 -0
- slidge/slixfix/xep_0317/stanza.py +28 -0
- slidge/{util → slixfix}/xep_0356_old/privilege.py +9 -7
- slidge/slixfix/xep_0424/__init__.py +9 -0
- slidge/slixfix/xep_0424/retraction.py +77 -0
- slidge/slixfix/xep_0424/stanza.py +28 -0
- slidge/slixfix/xep_0490/__init__.py +8 -0
- slidge/slixfix/xep_0490/mds.py +47 -0
- slidge/slixfix/xep_0490/stanza.py +17 -0
- slidge/util/__init__.py +4 -6
- slidge/util/archive_msg.py +61 -0
- slidge/util/conf.py +206 -0
- slidge/util/db.py +57 -76
- slidge/util/schema.sql +126 -0
- slidge/util/sql.py +508 -0
- slidge/util/test.py +215 -25
- slidge/util/types.py +177 -4
- slidge/util/util.py +225 -59
- slidge-0.1.1.dist-info/METADATA +110 -0
- slidge-0.1.1.dist-info/RECORD +96 -0
- {slidge-0.1.0b2.dist-info → slidge-0.1.1.dist-info}/WHEEL +1 -1
- slidge/core/contact.py +0 -891
- slidge/core/gateway.py +0 -916
- slidge/plugins/discord/__init__.py +0 -90
- slidge/plugins/discord/client.py +0 -108
- slidge/plugins/discord/session.py +0 -162
- slidge/plugins/dummy.py +0 -203
- slidge/plugins/facebook.py +0 -493
- slidge/plugins/hackernews.py +0 -213
- slidge/plugins/mattermost/__init__.py +0 -1
- slidge/plugins/mattermost/api.py +0 -280
- slidge/plugins/mattermost/gateway.py +0 -365
- slidge/plugins/mattermost/websocket.py +0 -252
- slidge/plugins/signal/__init__.py +0 -3
- slidge/plugins/signal/contact.py +0 -106
- slidge/plugins/signal/gateway.py +0 -282
- slidge/plugins/signal/session.py +0 -448
- slidge/plugins/signal/txt.py +0 -53
- slidge/plugins/skype.py +0 -325
- slidge/plugins/steam.py +0 -310
- slidge/plugins/telegram/__init__.py +0 -5
- slidge/plugins/telegram/client.py +0 -228
- slidge/plugins/telegram/config.py +0 -12
- slidge/plugins/telegram/contact.py +0 -176
- slidge/plugins/telegram/gateway.py +0 -150
- slidge/plugins/telegram/session.py +0 -256
- slidge/util/xep_0030/__init__.py +0 -13
- slidge/util/xep_0030/disco.py +0 -811
- slidge/util/xep_0030/stanza/__init__.py +0 -7
- slidge/util/xep_0030/stanza/info.py +0 -270
- slidge/util/xep_0030/stanza/items.py +0 -147
- slidge/util/xep_0030/static.py +0 -467
- slidge/util/xep_0055/__init__.py +0 -5
- slidge/util/xep_0055/search.py +0 -75
- slidge/util/xep_0055/stanza.py +0 -10
- slidge/util/xep_0077/stanza.py +0 -71
- slidge/util/xep_0084/avatar.py +0 -137
- slidge/util/xep_0084/stanza.py +0 -104
- slidge/util/xep_0115/__init__.py +0 -12
- slidge/util/xep_0115/caps.py +0 -379
- slidge/util/xep_0115/stanza.py +0 -16
- slidge/util/xep_0115/static.py +0 -137
- slidge/util/xep_0292/__init__.py +0 -1
- slidge/util/xep_0292/stanza.py +0 -167
- slidge/util/xep_0292/vcard4.py +0 -75
- slidge/util/xep_0333/__init__.py +0 -10
- slidge/util/xep_0333/markers.py +0 -96
- slidge/util/xep_0333/stanza.py +0 -34
- slidge/util/xep_0356/__init__.py +0 -7
- slidge/util/xep_0356/permissions.py +0 -35
- slidge/util/xep_0356/privilege.py +0 -160
- slidge/util/xep_0356/stanza.py +0 -44
- slidge/util/xep_0363/__init__.py +0 -16
- slidge/util/xep_0363/http_upload.py +0 -215
- slidge/util/xep_0363/stanza.py +0 -46
- slidge/util/xep_0461/__init__.py +0 -6
- slidge/util/xep_0461/reply.py +0 -48
- slidge/util/xep_0461/stanza.py +0 -47
- slidge-0.1.0b2.dist-info/METADATA +0 -171
- slidge-0.1.0b2.dist-info/RECORD +0 -81
- /slidge/{plugins/__init__.py → py.typed} +0 -0
- /slidge/{util → slixfix}/xep_0077/__init__.py +0 -0
- /slidge/{util → slixfix}/xep_0100/__init__.py +0 -0
- /slidge/{util → slixfix}/xep_0356_old/__init__.py +0 -0
- /slidge/{util → slixfix}/xep_0356_old/stanza.py +0 -0
- {slidge-0.1.0b2.dist-info → slidge-0.1.1.dist-info}/LICENSE +0 -0
- {slidge-0.1.0b2.dist-info → slidge-0.1.1.dist-info}/entry_points.txt +0 -0
slidge/core/session.py
CHANGED
@@ -1,55 +1,66 @@
|
|
1
|
-
import
|
1
|
+
import asyncio
|
2
2
|
import logging
|
3
|
-
from typing import
|
4
|
-
|
3
|
+
from typing import (
|
4
|
+
TYPE_CHECKING,
|
5
|
+
Any,
|
6
|
+
Generic,
|
7
|
+
Iterable,
|
8
|
+
NamedTuple,
|
9
|
+
Optional,
|
10
|
+
Union,
|
11
|
+
cast,
|
12
|
+
)
|
13
|
+
|
14
|
+
import aiohttp
|
5
15
|
from slixmpp import JID, Message
|
6
16
|
from slixmpp.exceptions import XMPPError
|
17
|
+
from slixmpp.types import PresenceShows
|
7
18
|
|
8
|
-
from ..
|
9
|
-
from ..
|
19
|
+
from ..command import SearchResult
|
20
|
+
from ..contact import LegacyContact, LegacyRoster
|
21
|
+
from ..group.bookmarks import LegacyBookmarks
|
22
|
+
from ..group.room import LegacyMUC
|
23
|
+
from ..util import ABCSubclassableOnceAtMost
|
10
24
|
from ..util.db import GatewayUser, user_store
|
11
|
-
from ..util.
|
25
|
+
from ..util.sql import SQLBiDict
|
26
|
+
from ..util.types import (
|
27
|
+
LegacyGroupIdType,
|
28
|
+
LegacyMessageType,
|
29
|
+
LegacyThreadType,
|
30
|
+
LinkPreview,
|
31
|
+
Mention,
|
32
|
+
PseudoPresenceShow,
|
33
|
+
RecipientType,
|
34
|
+
ResourceDict,
|
35
|
+
)
|
36
|
+
from ..util.util import deprecated
|
12
37
|
|
13
38
|
if TYPE_CHECKING:
|
14
|
-
from
|
15
|
-
from
|
16
|
-
|
17
|
-
GatewayType = TypeVar("GatewayType")
|
18
|
-
|
19
|
-
|
20
|
-
def ignore_message_to_component_and_sent_carbons(func):
|
21
|
-
@functools.wraps(func)
|
22
|
-
async def wrapped(self: T, msg: Message):
|
23
|
-
if msg.get_to() != self.xmpp.boundjid.bare:
|
24
|
-
if (i := msg.get_id()) in self.ignore_messages:
|
25
|
-
self.log.debug("Ignored message: %s", i)
|
26
|
-
self.ignore_messages.remove(i)
|
27
|
-
else:
|
28
|
-
log.debug("FUNC: %s", func)
|
29
|
-
return await func(self, msg)
|
30
|
-
else:
|
31
|
-
log.debug("Ignoring message to component: %s %s", self, msg)
|
32
|
-
|
33
|
-
return wrapped
|
39
|
+
from ..group.participant import LegacyParticipant
|
40
|
+
from ..util.types import Sender
|
41
|
+
from .gateway import BaseGateway
|
34
42
|
|
35
43
|
|
36
|
-
|
44
|
+
class CachedPresence(NamedTuple):
|
45
|
+
status: Optional[str]
|
46
|
+
show: Optional[str]
|
47
|
+
kwargs: dict[str, Any]
|
37
48
|
|
38
49
|
|
39
50
|
class BaseSession(
|
40
|
-
Generic[
|
41
|
-
metaclass=ABCSubclassableOnceAtMost,
|
51
|
+
Generic[LegacyMessageType, RecipientType], metaclass=ABCSubclassableOnceAtMost
|
42
52
|
):
|
43
53
|
"""
|
44
|
-
|
54
|
+
The session of a registered :term:`User`.
|
45
55
|
|
46
|
-
|
47
|
-
component, as per :xep:`0100`.
|
56
|
+
Represents a gateway user logged in to the legacy network and performing actions.
|
48
57
|
|
49
|
-
|
58
|
+
Will be instantiated automatically on slidge startup for each registered user,
|
59
|
+
or upon registration for new (validated) users.
|
60
|
+
|
61
|
+
Must be subclassed for a functional :term:`Legacy Module`.
|
50
62
|
"""
|
51
63
|
|
52
|
-
sent: BiDict[LegacyMessageType, str]
|
53
64
|
"""
|
54
65
|
Since we cannot set the XMPP ID of messages sent by XMPP clients, we need to keep a mapping
|
55
66
|
between XMPP IDs and legacy message IDs if we want to further refer to a message that was sent
|
@@ -57,489 +68,701 @@ class BaseSession(
|
|
57
68
|
the official client of a legacy network.
|
58
69
|
"""
|
59
70
|
|
60
|
-
xmpp: "
|
71
|
+
xmpp: "BaseGateway"
|
61
72
|
"""
|
62
73
|
The gateway instance singleton. Use it for low-level XMPP calls or custom methods that are not
|
63
74
|
session-specific.
|
64
75
|
"""
|
65
76
|
|
66
|
-
|
67
|
-
self._roster_cls: Type[
|
68
|
-
LegacyRosterType
|
69
|
-
] = LegacyRoster.get_self_or_unique_subclass()
|
77
|
+
http: aiohttp.ClientSession
|
70
78
|
|
79
|
+
MESSAGE_IDS_ARE_THREAD_IDS = False
|
80
|
+
"""
|
81
|
+
Set this to True if the legacy service uses message IDs as thread IDs,
|
82
|
+
eg Mattermost, where you can only 'create a thread' by replying to the message,
|
83
|
+
in which case the message ID is also a thread ID (and all messages are potential
|
84
|
+
threads).
|
85
|
+
"""
|
86
|
+
SPECIAL_MSG_ID_PREFIX: Optional[str] = None
|
87
|
+
"""
|
88
|
+
If you set this, XMPP message IDs starting with this won't be converted to legacy ID,
|
89
|
+
but passed as is to :meth:`.on_react`, and usual checks for emoji restriction won't be
|
90
|
+
applied.
|
91
|
+
This can be used to implement voting in polls in a hacky way.
|
92
|
+
"""
|
93
|
+
|
94
|
+
def __init__(self, user: GatewayUser):
|
71
95
|
self.log = logging.getLogger(user.bare_jid)
|
72
96
|
|
73
97
|
self.user = user
|
74
|
-
self.sent
|
75
|
-
|
76
|
-
|
98
|
+
self.sent = SQLBiDict[LegacyMessageType, str](
|
99
|
+
"session_message_sent", "legacy_id", "xmpp_id", self.user
|
100
|
+
)
|
101
|
+
# message ids (*not* stanza-ids), needed for last msg correction
|
102
|
+
self.muc_sent_msg_ids = SQLBiDict[LegacyMessageType, str](
|
103
|
+
"session_message_sent_muc", "legacy_id", "xmpp_id", self.user
|
104
|
+
)
|
77
105
|
|
78
106
|
self.ignore_messages = set[str]()
|
79
107
|
|
80
|
-
self.contacts:
|
81
|
-
self.
|
108
|
+
self.contacts: LegacyRoster = LegacyRoster.get_self_or_unique_subclass()(self)
|
109
|
+
self._logged = False
|
110
|
+
self.__reset_ready()
|
82
111
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
Convert a legacy msg ID to a valid XMPP msg ID.
|
87
|
-
Needed for read marks and message corrections.
|
88
|
-
|
89
|
-
The default implementation just converts the legacy ID to a :class:`str`,
|
90
|
-
but this should be overridden in case some characters needs to be escaped,
|
91
|
-
or to add some additional, legacy network-specific logic.
|
112
|
+
self.bookmarks: LegacyBookmarks = LegacyBookmarks.get_self_or_unique_subclass()(
|
113
|
+
self
|
114
|
+
)
|
92
115
|
|
93
|
-
|
94
|
-
:return: Should return a string that is usable as an XMPP stanza ID
|
95
|
-
"""
|
96
|
-
return str(legacy_msg_id)
|
116
|
+
self.http = self.xmpp.http
|
97
117
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
Needed for read marks and message corrections.
|
118
|
+
self.threads = SQLBiDict[str, LegacyThreadType]( # type:ignore
|
119
|
+
"session_thread_sent_muc", "legacy_id", "xmpp_id", self.user
|
120
|
+
)
|
121
|
+
self.thread_creation_lock = asyncio.Lock()
|
103
122
|
|
104
|
-
|
105
|
-
but this should be overridden in case some characters needs to be escaped,
|
106
|
-
or to add some additional, legacy network-specific logic.
|
123
|
+
self.__cached_presence: Optional[CachedPresence] = None
|
107
124
|
|
108
|
-
|
125
|
+
self.avatar_hash: Optional[str] = None
|
109
126
|
|
110
|
-
|
111
|
-
:return: An ID that can be used to identify a message on the legacy network
|
112
|
-
"""
|
113
|
-
return i
|
127
|
+
self.__tasks = set[asyncio.Task]()
|
114
128
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
raise XMPPError(
|
119
|
-
text="User not found", condition="subscription-required", etype="auth"
|
120
|
-
)
|
129
|
+
def __remove_task(self, fut):
|
130
|
+
self.log.debug("Removing fut %s", fut)
|
131
|
+
self.__tasks.remove(fut)
|
121
132
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
133
|
+
def create_task(self, coro) -> None:
|
134
|
+
task = self.xmpp.loop.create_task(coro)
|
135
|
+
self.__tasks.add(task)
|
136
|
+
self.log.debug("Creating task %s", task)
|
137
|
+
task.add_done_callback(lambda _: self.__remove_task(task))
|
126
138
|
|
127
|
-
|
128
|
-
|
129
|
-
|
139
|
+
def cancel_all_tasks(self):
|
140
|
+
for task in self.__tasks:
|
141
|
+
task.cancel()
|
130
142
|
|
131
|
-
|
132
|
-
def from_stanza(cls: Type[T], s) -> T:
|
143
|
+
async def login(self) -> Optional[str]:
|
133
144
|
"""
|
134
|
-
|
145
|
+
Logs in the gateway user to the legacy network.
|
135
146
|
|
136
|
-
|
147
|
+
Triggered when the gateway start and on user registration.
|
148
|
+
It is recommended that this function returns once the user is logged in,
|
149
|
+
so if you need to await forever (for instance to listen to incoming events),
|
150
|
+
it's a good idea to wrap your listener in an asyncio.Task.
|
137
151
|
|
138
|
-
:
|
139
|
-
:return:
|
152
|
+
:return: Optionally, a text to use as the gateway status, e.g., "Connected as 'dude@legacy.network'"
|
140
153
|
"""
|
141
|
-
|
154
|
+
raise NotImplementedError
|
142
155
|
|
143
|
-
|
144
|
-
def from_jid(cls: Type[T], jid: JID) -> T:
|
156
|
+
async def logout(self):
|
145
157
|
"""
|
146
|
-
|
158
|
+
Logs out the gateway user from the legacy network.
|
147
159
|
|
148
|
-
|
149
|
-
|
150
|
-
:param jid:
|
151
|
-
:return:
|
160
|
+
Called on gateway shutdown.
|
152
161
|
"""
|
153
|
-
|
162
|
+
raise NotImplementedError
|
154
163
|
|
155
|
-
|
156
|
-
|
164
|
+
async def on_text(
|
165
|
+
self,
|
166
|
+
chat: RecipientType,
|
167
|
+
text: str,
|
168
|
+
*,
|
169
|
+
reply_to_msg_id: Optional[LegacyMessageType] = None,
|
170
|
+
reply_to_fallback_text: Optional[str] = None,
|
171
|
+
reply_to: Optional["Sender"] = None,
|
172
|
+
thread: Optional[LegacyThreadType] = None,
|
173
|
+
link_previews: Iterable[LinkPreview] = (),
|
174
|
+
mentions: Optional[list[Mention]] = None,
|
175
|
+
) -> Optional[LegacyMessageType]:
|
157
176
|
"""
|
158
|
-
|
177
|
+
Triggered when the user sends a text message from XMPP to a bridged entity, e.g.
|
178
|
+
to ``translated_user_name@slidge.example.com``, or ``translated_group_name@slidge.example.com``
|
159
179
|
|
160
|
-
|
180
|
+
Override this and implement sending a message to the legacy network in this method.
|
161
181
|
|
162
|
-
:param
|
163
|
-
:
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
del session
|
182
|
+
:param text: Content of the message
|
183
|
+
:param chat: Recipient of the message. :class:`.LegacyContact` instance for 1:1 chat,
|
184
|
+
:class:`.MUC` instance for groups.
|
185
|
+
:param reply_to_msg_id: A legacy message ID if the message references (quotes)
|
186
|
+
another message (:xep:`0461`)
|
187
|
+
:param reply_to_fallback_text: Content of the quoted text. Not necessarily set
|
188
|
+
by XMPP clients
|
189
|
+
:param reply_to: Author of the quoted message. :class:`LegacyContact` instance for
|
190
|
+
1:1 chat, :class:`LegacyParticipant` instance for groups.
|
191
|
+
If `None`, should be interpreted as a self-reply if reply_to_msg_id is not None.
|
192
|
+
:param link_previews: A list of sender-generated link previews.
|
193
|
+
At the time of writing, only `Cheogram <https://wiki.soprani.ca/CheogramApp/LinkPreviews>`_
|
194
|
+
supports it.
|
195
|
+
:param mentions: (only for groups) A list of Contacts mentioned by their
|
196
|
+
nicknames.
|
197
|
+
:param thread:
|
179
198
|
|
180
|
-
|
181
|
-
|
199
|
+
:return: An ID of some sort that can be used later to ack and mark the message
|
200
|
+
as read by the user
|
182
201
|
"""
|
183
|
-
|
202
|
+
raise NotImplementedError
|
184
203
|
|
185
|
-
|
186
|
-
|
204
|
+
send_text = deprecated("BaseSession.send_text", on_text)
|
205
|
+
|
206
|
+
async def on_file(
|
207
|
+
self,
|
208
|
+
chat: RecipientType,
|
209
|
+
url: str,
|
210
|
+
*,
|
211
|
+
http_response: aiohttp.ClientResponse,
|
212
|
+
reply_to_msg_id: Optional[LegacyMessageType] = None,
|
213
|
+
reply_to_fallback_text: Optional[str] = None,
|
214
|
+
reply_to: Optional[Union["LegacyContact", "LegacyParticipant"]] = None,
|
215
|
+
thread: Optional[LegacyThreadType] = None,
|
216
|
+
) -> Optional[LegacyMessageType]:
|
187
217
|
"""
|
188
|
-
|
189
|
-
# ignore last message correction and retraction (handled by a specific method)
|
190
|
-
return
|
218
|
+
Triggered when the user sends a file using HTTP Upload (:xep:`0363`)
|
191
219
|
|
192
|
-
|
220
|
+
:param url: URL of the file
|
221
|
+
:param chat: See :meth:`.BaseSession.on_text`
|
222
|
+
:param http_response: The HTTP GET response object on the URL
|
223
|
+
:param reply_to_msg_id: See :meth:`.BaseSession.on_text`
|
224
|
+
:param reply_to_fallback_text: See :meth:`.BaseSession.on_text`
|
225
|
+
:param reply_to: See :meth:`.BaseSession.on_text`
|
226
|
+
:param thread:
|
193
227
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
and contact.REPLIES
|
199
|
-
):
|
200
|
-
text = m["feature_fallback"].get_stripped_body()
|
228
|
+
:return: An ID of some sort that can be used later to ack and mark the message
|
229
|
+
as read by the user
|
230
|
+
"""
|
231
|
+
raise NotImplementedError
|
201
232
|
|
202
|
-
|
203
|
-
legacy_msg_id = await self.send_file(
|
204
|
-
url, contact, reply_to_msg_id=m["reply"]["id"] or None
|
205
|
-
)
|
206
|
-
elif text:
|
207
|
-
legacy_msg_id = await self.send_text(
|
208
|
-
text, contact, reply_to_msg_id=m["reply"]["id"] or None
|
209
|
-
)
|
210
|
-
else:
|
211
|
-
log.debug("Ignoring %s", m)
|
212
|
-
return
|
213
|
-
if legacy_msg_id is not None:
|
214
|
-
self.sent[legacy_msg_id] = m.get_id()
|
233
|
+
send_file = deprecated("BaseSession.send_file", on_file)
|
215
234
|
|
216
|
-
|
217
|
-
|
235
|
+
async def on_active(
|
236
|
+
self, chat: RecipientType, thread: Optional[LegacyThreadType] = None
|
237
|
+
):
|
218
238
|
"""
|
219
|
-
|
239
|
+
Triggered when the user sends an 'active' chat state (:xep:`0085`)
|
220
240
|
|
221
|
-
:param
|
222
|
-
:
|
241
|
+
:param chat: See :meth:`.BaseSession.on_text`
|
242
|
+
:param thread:
|
223
243
|
"""
|
224
|
-
|
225
|
-
await self.active(self.contacts.by_stanza(m))
|
244
|
+
raise NotImplementedError
|
226
245
|
|
227
|
-
|
228
|
-
async def inactive_from_msg(self, m: Message):
|
229
|
-
"""
|
230
|
-
Meant to be called from :class:`BaseGateway` only.
|
246
|
+
active = deprecated("BaseSession.active", on_active)
|
231
247
|
|
232
|
-
|
233
|
-
:
|
248
|
+
async def on_inactive(
|
249
|
+
self, chat: RecipientType, thread: Optional[LegacyThreadType] = None
|
250
|
+
):
|
234
251
|
"""
|
235
|
-
|
236
|
-
await self.inactive(self.contacts.by_stanza(m))
|
252
|
+
Triggered when the user sends an 'inactive' chat state (:xep:`0085`)
|
237
253
|
|
238
|
-
|
239
|
-
|
254
|
+
:param chat: See :meth:`.BaseSession.on_text`
|
255
|
+
:param thread:
|
240
256
|
"""
|
241
|
-
|
257
|
+
raise NotImplementedError
|
242
258
|
|
243
|
-
|
244
|
-
:return:
|
245
|
-
"""
|
246
|
-
if m.get_to() != self.xmpp.boundjid.bare:
|
247
|
-
log.debug("COMPOSING: %s", self.contacts.by_stanza(m))
|
248
|
-
await self.composing(self.contacts.by_stanza(m))
|
259
|
+
inactive = deprecated("BaseSession.inactive", on_inactive)
|
249
260
|
|
250
|
-
|
251
|
-
|
261
|
+
async def on_composing(
|
262
|
+
self, chat: RecipientType, thread: Optional[LegacyThreadType] = None
|
263
|
+
):
|
252
264
|
"""
|
253
|
-
|
265
|
+
Triggered when the user starts typing in a legacy chat (:xep:`0085`)
|
254
266
|
|
255
|
-
:param
|
256
|
-
:
|
267
|
+
:param chat: See :meth:`.BaseSession.on_text`
|
268
|
+
:param thread:
|
257
269
|
"""
|
258
|
-
|
259
|
-
await self.paused(self.contacts.by_stanza(m))
|
270
|
+
raise NotImplementedError
|
260
271
|
|
261
|
-
|
262
|
-
async def displayed_from_msg(self, m: Message):
|
263
|
-
"""
|
264
|
-
Meant to be called from :class:`BaseGateway` only.
|
272
|
+
composing = deprecated("BaseSession.composing", on_composing)
|
265
273
|
|
266
|
-
|
267
|
-
:
|
274
|
+
async def on_paused(
|
275
|
+
self, chat: RecipientType, thread: Optional[LegacyThreadType] = None
|
276
|
+
):
|
268
277
|
"""
|
269
|
-
|
270
|
-
try:
|
271
|
-
legacy_msg_id = self.xmpp_msg_id_to_legacy_msg_id(displayed_msg_id)
|
272
|
-
except NotImplementedError:
|
273
|
-
log.debug("Couldn't convert xmpp msg ID to legacy ID, ignoring read mark")
|
274
|
-
return
|
278
|
+
Triggered when the user pauses typing in a legacy chat (:xep:`0085`)
|
275
279
|
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
xmpp_id = m["replace"]["id"]
|
281
|
-
legacy_id = self.sent.inverse.get(xmpp_id)
|
282
|
-
if legacy_id is None:
|
283
|
-
log.debug("Did not find legacy ID to correct")
|
284
|
-
new_legacy_msg_id = await self.send_text(
|
285
|
-
m["body"], self.contacts.by_stanza(m)
|
286
|
-
)
|
287
|
-
else:
|
288
|
-
new_legacy_msg_id = await self.correct(
|
289
|
-
m["body"], legacy_id, self.contacts.by_stanza(m)
|
290
|
-
)
|
291
|
-
if new_legacy_msg_id is not None:
|
292
|
-
self.sent[new_legacy_msg_id] = m.get_id()
|
293
|
-
|
294
|
-
@ignore_message_to_component_and_sent_carbons
|
295
|
-
async def react_from_msg(self, m: Message):
|
296
|
-
react_to = m["reactions"]["id"]
|
297
|
-
if (legacy_id := self.sent.inverse.get(react_to)) is None:
|
298
|
-
log.debug("Cannot find the XMPP ID of this msg: %s", react_to)
|
299
|
-
try:
|
300
|
-
legacy_id = self.xmpp_msg_id_to_legacy_msg_id(react_to)
|
301
|
-
except ValueError:
|
302
|
-
log.warning(
|
303
|
-
"Could not convert legacy ID, xmpp reaction was not sent: %s", m
|
304
|
-
)
|
305
|
-
return
|
306
|
-
await self.react(
|
307
|
-
legacy_id, [r["value"] for r in m["reactions"]], self.contacts.by_stanza(m)
|
308
|
-
)
|
280
|
+
:param chat: See :meth:`.BaseSession.on_text`
|
281
|
+
:param thread:
|
282
|
+
"""
|
283
|
+
raise NotImplementedError
|
309
284
|
|
310
|
-
|
311
|
-
async def retract_from_msg(self, m: Message):
|
312
|
-
xmpp_id = m["apply_to"]["id"]
|
313
|
-
if (legacy_id := self.sent.inverse.get(xmpp_id)) is None:
|
314
|
-
log.debug(
|
315
|
-
"Cannot find the XMPP ID of this msg: %s, cannot retract", xmpp_id
|
316
|
-
)
|
317
|
-
return
|
318
|
-
await self.retract(legacy_id, self.contacts.by_stanza(m))
|
285
|
+
paused = deprecated("BaseSession.paused", on_paused)
|
319
286
|
|
320
|
-
def
|
287
|
+
async def on_displayed(
|
321
288
|
self,
|
322
|
-
|
323
|
-
|
324
|
-
|
289
|
+
chat: RecipientType,
|
290
|
+
legacy_msg_id: LegacyMessageType,
|
291
|
+
thread: Optional[LegacyThreadType] = None,
|
325
292
|
):
|
326
293
|
"""
|
327
|
-
|
294
|
+
Triggered when the user reads a message in a legacy chat. (:xep:`0333`)
|
328
295
|
|
329
|
-
|
330
|
-
|
331
|
-
:
|
332
|
-
|
333
|
-
|
334
|
-
"""
|
335
|
-
self.xmpp.send_presence(
|
336
|
-
pto=self.user.bare_jid, pstatus=status, pshow=show, **kwargs
|
337
|
-
)
|
296
|
+
This is only possible if a valid ``legacy_msg_id`` was passed when
|
297
|
+
transmitting a message from a legacy chat to the user, eg in
|
298
|
+
:meth:`slidge.contact.LegacyContact.send_text`
|
299
|
+
or
|
300
|
+
:meth:`slidge.group.LegacyParticipant.send_text`.
|
338
301
|
|
339
|
-
|
302
|
+
:param chat: See :meth:`.BaseSession.on_text`
|
303
|
+
:param legacy_msg_id: Identifier of the message/
|
304
|
+
:param thread:
|
340
305
|
"""
|
341
|
-
|
306
|
+
raise NotImplementedError
|
342
307
|
|
343
|
-
|
308
|
+
displayed = deprecated("BaseSession.displayed", on_displayed)
|
344
309
|
|
345
|
-
|
310
|
+
async def on_correct(
|
311
|
+
self,
|
312
|
+
chat: RecipientType,
|
313
|
+
text: str,
|
314
|
+
legacy_msg_id: LegacyMessageType,
|
315
|
+
*,
|
316
|
+
thread: Optional[LegacyThreadType] = None,
|
317
|
+
link_previews: Iterable[LinkPreview] = (),
|
318
|
+
mentions: Optional[list[Mention]] = None,
|
319
|
+
) -> Optional[LegacyMessageType]:
|
346
320
|
"""
|
347
|
-
|
348
|
-
mto=self.user.jid, mbody=text, mfrom=self.xmpp.boundjid, **msg_kwargs
|
349
|
-
)
|
321
|
+
Triggered when the user corrects a message using :xep:`0308`
|
350
322
|
|
351
|
-
|
323
|
+
This is only possible if a valid ``legacy_msg_id`` was returned by
|
324
|
+
:meth:`.on_text`.
|
325
|
+
|
326
|
+
:param chat: See :meth:`.BaseSession.on_text`
|
327
|
+
:param text: The new text
|
328
|
+
:param legacy_msg_id: Identifier of the edited message
|
329
|
+
:param thread:
|
330
|
+
:param link_previews: A list of sender-generated link previews.
|
331
|
+
At the time of writing, only `Cheogram <https://wiki.soprani.ca/CheogramApp/LinkPreviews>`_
|
332
|
+
supports it.
|
333
|
+
:param mentions: (only for groups) A list of Contacts mentioned by their
|
334
|
+
nicknames.
|
352
335
|
"""
|
353
|
-
|
336
|
+
raise NotImplementedError
|
354
337
|
|
355
|
-
|
338
|
+
correct = deprecated("BaseSession.correct", on_correct)
|
356
339
|
|
357
|
-
|
358
|
-
|
359
|
-
:
|
340
|
+
async def on_react(
|
341
|
+
self,
|
342
|
+
chat: RecipientType,
|
343
|
+
legacy_msg_id: LegacyMessageType,
|
344
|
+
emojis: list[str],
|
345
|
+
thread: Optional[LegacyThreadType] = None,
|
346
|
+
):
|
360
347
|
"""
|
361
|
-
|
348
|
+
Triggered when the user sends message reactions (:xep:`0444`).
|
362
349
|
|
363
|
-
|
350
|
+
:param chat: See :meth:`.BaseSession.on_text`
|
351
|
+
:param thread:
|
352
|
+
:param legacy_msg_id: ID of the message the user reacts to
|
353
|
+
:param emojis: Unicode characters representing reactions to the message ``legacy_msg_id``.
|
354
|
+
An empty string means "no reaction", ie, remove all reactions if any were present before
|
364
355
|
"""
|
365
|
-
|
366
|
-
``self.user``
|
356
|
+
raise NotImplementedError
|
367
357
|
|
368
|
-
|
369
|
-
"""
|
370
|
-
await self.xmpp.send_qr(text, mto=self.user.jid)
|
358
|
+
react = deprecated("BaseSession.react", on_react)
|
371
359
|
|
372
|
-
def
|
360
|
+
async def on_retract(
|
361
|
+
self,
|
362
|
+
chat: RecipientType,
|
363
|
+
legacy_msg_id: LegacyMessageType,
|
364
|
+
thread: Optional[LegacyThreadType] = None,
|
365
|
+
):
|
373
366
|
"""
|
374
|
-
|
367
|
+
Triggered when the user retracts (:xep:`0424`) a message.
|
375
368
|
|
376
|
-
|
377
|
-
|
369
|
+
:param chat: See :meth:`.BaseSession.on_text`
|
370
|
+
:param thread:
|
371
|
+
:param legacy_msg_id: Legacy ID of the retracted message
|
378
372
|
"""
|
379
|
-
|
373
|
+
raise NotImplementedError
|
380
374
|
|
381
|
-
|
382
|
-
"""
|
383
|
-
Login the gateway user to the legacy network.
|
375
|
+
retract = deprecated("BaseSession.retract", on_retract)
|
384
376
|
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
377
|
+
async def on_presence(
|
378
|
+
self,
|
379
|
+
resource: str,
|
380
|
+
show: PseudoPresenceShow,
|
381
|
+
status: str,
|
382
|
+
resources: dict[str, ResourceDict],
|
383
|
+
merged_resource: Optional[ResourceDict],
|
384
|
+
):
|
385
|
+
"""
|
386
|
+
Called when the gateway component receives a presence, ie, when
|
387
|
+
one of the user's clients goes online of offline, or changes its
|
388
|
+
status.
|
389
389
|
|
390
|
-
:
|
390
|
+
:param resource: The XMPP client identifier, arbitrary string.
|
391
|
+
:param show: The presence ``<show>``, if available. If the resource is
|
392
|
+
just 'available' without any ``<show>`` element, this is an empty
|
393
|
+
str.
|
394
|
+
:param status: A status message, like a deeply profound quote, eg,
|
395
|
+
"Roses are red, violets are blue, [INSERT JOKE]".
|
396
|
+
:param resources: A summary of all the resources for this user.
|
397
|
+
:param merged_resource: A global presence for the user account,
|
398
|
+
following rules described in :meth:`merge_resources`
|
391
399
|
"""
|
392
400
|
raise NotImplementedError
|
393
401
|
|
394
|
-
|
402
|
+
presence = deprecated("BaseSession.presence", on_presence)
|
403
|
+
|
404
|
+
async def on_search(self, form_values: dict[str, str]) -> Optional[SearchResult]:
|
395
405
|
"""
|
396
|
-
|
406
|
+
Triggered when the user uses Jabber Search (:xep:`0055`) on the component
|
397
407
|
|
398
|
-
|
408
|
+
Form values is a dict in which keys are defined in :attr:`.BaseGateway.SEARCH_FIELDS`
|
409
|
+
|
410
|
+
:param form_values: search query, defined for a specific plugin by overriding
|
411
|
+
in :attr:`.BaseGateway.SEARCH_FIELDS`
|
412
|
+
:return:
|
399
413
|
"""
|
400
414
|
raise NotImplementedError
|
401
415
|
|
402
|
-
|
403
|
-
"""
|
404
|
-
Logout then re-login
|
416
|
+
search = deprecated("BaseSession.search", on_search)
|
405
417
|
|
406
|
-
|
418
|
+
async def on_avatar(
|
419
|
+
self,
|
420
|
+
bytes_: Optional[bytes],
|
421
|
+
hash_: Optional[str],
|
422
|
+
type_: Optional[str],
|
423
|
+
width: Optional[int],
|
424
|
+
height: Optional[int],
|
425
|
+
) -> None:
|
426
|
+
"""
|
427
|
+
Triggered when the user uses modifies their avatar via :xep:`0084`.
|
428
|
+
|
429
|
+
:param bytes_: The data of the avatar. According to the spec, this
|
430
|
+
should always be a PNG, but some implementations do not respect
|
431
|
+
that. If `None` it means the user has unpublished their avatar.
|
432
|
+
:param hash_: The SHA1 hash of the avatar data. This is an identifier of
|
433
|
+
the avatar.
|
434
|
+
:param type_: The MIME type of the avatar.
|
435
|
+
:param width: The width of the avatar image.
|
436
|
+
:param height: The height of the avatar image.
|
407
437
|
"""
|
408
|
-
|
438
|
+
raise NotImplementedError
|
409
439
|
|
410
|
-
async def
|
411
|
-
self,
|
412
|
-
|
413
|
-
c: LegacyContactType,
|
414
|
-
*,
|
415
|
-
reply_to_msg_id: Optional[LegacyMessageType] = None,
|
416
|
-
) -> Optional[LegacyMessageType]:
|
440
|
+
async def on_moderate(
|
441
|
+
self, muc: LegacyMUC, legacy_msg_id: LegacyMessageType, reason: Optional[str]
|
442
|
+
):
|
417
443
|
"""
|
418
|
-
Triggered when the user
|
419
|
-
|
444
|
+
Triggered when the user attempts to retract a message that was sent in
|
445
|
+
a MUC using :xep:`0425`.
|
420
446
|
|
421
|
-
|
447
|
+
If retraction is not possible, this should raise the appropriate
|
448
|
+
XMPPError with a human-readable message.
|
422
449
|
|
423
|
-
:
|
424
|
-
:
|
425
|
-
|
426
|
-
|
427
|
-
|
450
|
+
NB: the legacy module is responsible for calling
|
451
|
+
:method:`LegacyParticipant.moderate` when this is successful, because
|
452
|
+
slidge will acknowledge the moderation IQ, but will not send the
|
453
|
+
moderation message from the MUC automatically.
|
454
|
+
|
455
|
+
:param muc: The MUC in which the message was sent
|
456
|
+
:param legacy_msg_id: The legacy ID of the message to be retracted
|
457
|
+
:param reason: Optionally, a reason for the moderation, given by the
|
458
|
+
user-moderator.
|
428
459
|
"""
|
429
460
|
raise NotImplementedError
|
430
461
|
|
431
|
-
async def
|
432
|
-
self,
|
433
|
-
|
434
|
-
c: LegacyContactType,
|
435
|
-
*,
|
436
|
-
reply_to_msg_id: Optional[LegacyMessageType] = None,
|
437
|
-
) -> Optional[LegacyMessageType]:
|
462
|
+
async def on_create_group(
|
463
|
+
self, name: str, contacts: list[LegacyContact]
|
464
|
+
) -> LegacyGroupIdType:
|
438
465
|
"""
|
439
|
-
Triggered when the user
|
466
|
+
Triggered when the user request the creation of a group via the
|
467
|
+
dedicated :term:`Command`.
|
440
468
|
|
441
|
-
:param
|
442
|
-
:param
|
443
|
-
:param reply_to_msg_id:
|
444
|
-
:return: An ID of some sort that can be used later to ack and mark the message
|
445
|
-
as read by the user
|
469
|
+
:param name: Name of the group
|
470
|
+
:param contacts: list of contacts that should be members of the group
|
446
471
|
"""
|
447
472
|
raise NotImplementedError
|
448
473
|
|
449
|
-
async def
|
474
|
+
async def on_invitation(
|
475
|
+
self, contact: LegacyContact, muc: LegacyMUC, reason: Optional[str]
|
476
|
+
):
|
450
477
|
"""
|
451
|
-
Triggered when the user
|
478
|
+
Triggered when the user invites a :term:`Contact` to a legacy MUC via
|
479
|
+
:xep:`0249`.
|
452
480
|
|
453
|
-
|
454
|
-
|
455
|
-
|
481
|
+
The default implementation calls :meth:`LegacyMUC.on_set_affiliation`
|
482
|
+
with the 'member' affiliation. Override if you want to customize this
|
483
|
+
behaviour.
|
456
484
|
|
457
|
-
|
485
|
+
:param contact: The invitee
|
486
|
+
:param muc: The group
|
487
|
+
:param reason: Optionally, a reason
|
458
488
|
"""
|
459
|
-
|
489
|
+
await muc.on_set_affiliation(contact, "member", reason, None)
|
460
490
|
|
461
|
-
|
491
|
+
def __reset_ready(self):
|
492
|
+
self.ready = self.xmpp.loop.create_future()
|
493
|
+
|
494
|
+
@property
|
495
|
+
def logged(self):
|
496
|
+
return self._logged
|
497
|
+
|
498
|
+
@logged.setter
|
499
|
+
def logged(self, v: bool):
|
500
|
+
self._logged = v
|
501
|
+
if self.ready.done():
|
502
|
+
if v:
|
503
|
+
return
|
504
|
+
self.__reset_ready()
|
505
|
+
else:
|
506
|
+
if v:
|
507
|
+
self.ready.set_result(True)
|
508
|
+
|
509
|
+
def __repr__(self):
|
510
|
+
return f"<Session of {self.user}>"
|
511
|
+
|
512
|
+
def shutdown(self) -> asyncio.Task:
|
513
|
+
for c in self.contacts:
|
514
|
+
c.offline()
|
515
|
+
for m in self.bookmarks:
|
516
|
+
m.shutdown()
|
517
|
+
return self.xmpp.loop.create_task(self.logout())
|
518
|
+
|
519
|
+
@staticmethod
|
520
|
+
def legacy_to_xmpp_msg_id(legacy_msg_id: LegacyMessageType) -> str:
|
462
521
|
"""
|
463
|
-
|
522
|
+
Convert a legacy msg ID to a valid XMPP msg ID.
|
523
|
+
Needed for read marks, retractions and message corrections.
|
524
|
+
|
525
|
+
The default implementation just converts the legacy ID to a :class:`str`,
|
526
|
+
but this should be overridden in case some characters needs to be escaped,
|
527
|
+
or to add some additional,
|
528
|
+
:term:`legacy network <Legacy Network`>-specific logic.
|
464
529
|
|
465
|
-
|
530
|
+
:param legacy_msg_id:
|
531
|
+
:return: A string that is usable as an XMPP stanza ID
|
466
532
|
"""
|
467
|
-
|
533
|
+
return str(legacy_msg_id)
|
534
|
+
|
535
|
+
legacy_msg_id_to_xmpp_msg_id = staticmethod(
|
536
|
+
deprecated("BaseSession.legacy_msg_id_to_xmpp_msg_id", legacy_to_xmpp_msg_id)
|
537
|
+
)
|
468
538
|
|
469
|
-
|
539
|
+
@staticmethod
|
540
|
+
def xmpp_to_legacy_msg_id(i: str) -> LegacyMessageType:
|
470
541
|
"""
|
471
|
-
|
542
|
+
Convert a legacy XMPP ID to a valid XMPP msg ID.
|
543
|
+
Needed for read marks and message corrections.
|
544
|
+
|
545
|
+
The default implementation just converts the legacy ID to a :class:`str`,
|
546
|
+
but this should be overridden in case some characters needs to be escaped,
|
547
|
+
or to add some additional,
|
548
|
+
:term:`legacy network <Legacy Network`>-specific logic.
|
549
|
+
|
550
|
+
The default implementation is an identity function.
|
472
551
|
|
473
|
-
|
552
|
+
:param i: The XMPP stanza ID
|
553
|
+
:return: An ID that can be used to identify a message on the legacy network
|
474
554
|
"""
|
475
|
-
|
555
|
+
return cast(LegacyMessageType, i)
|
556
|
+
|
557
|
+
xmpp_msg_id_to_legacy_msg_id = staticmethod(
|
558
|
+
deprecated("BaseSession.xmpp_msg_id_to_legacy_msg_id", xmpp_to_legacy_msg_id)
|
559
|
+
)
|
560
|
+
|
561
|
+
def raise_if_not_logged(self):
|
562
|
+
if not self.logged:
|
563
|
+
raise XMPPError(
|
564
|
+
"internal-server-error",
|
565
|
+
text="You are not logged to the legacy network",
|
566
|
+
)
|
567
|
+
|
568
|
+
@classmethod
|
569
|
+
def _from_user_or_none(cls, user):
|
570
|
+
if user is None:
|
571
|
+
log.debug("user not found", stack_info=True)
|
572
|
+
raise XMPPError(text="User not found", condition="subscription-required")
|
573
|
+
|
574
|
+
session = _sessions.get(user)
|
575
|
+
if session is None:
|
576
|
+
_sessions[user] = session = cls(user)
|
577
|
+
return session
|
476
578
|
|
477
|
-
|
579
|
+
@classmethod
|
580
|
+
def from_user(cls, user):
|
581
|
+
return cls._from_user_or_none(user)
|
582
|
+
|
583
|
+
@classmethod
|
584
|
+
def from_stanza(cls, s) -> "BaseSession":
|
585
|
+
# """
|
586
|
+
# Get a user's :class:`.LegacySession` using the "from" field of a stanza
|
587
|
+
#
|
588
|
+
# Meant to be called from :class:`BaseGateway` only.
|
589
|
+
#
|
590
|
+
# :param s:
|
591
|
+
# :return:
|
592
|
+
# """
|
593
|
+
return cls._from_user_or_none(user_store.get_by_stanza(s))
|
594
|
+
|
595
|
+
@classmethod
|
596
|
+
def from_jid(cls, jid: JID) -> "BaseSession":
|
597
|
+
# """
|
598
|
+
# Get a user's :class:`.LegacySession` using its jid
|
599
|
+
#
|
600
|
+
# Meant to be called from :class:`BaseGateway` only.
|
601
|
+
#
|
602
|
+
# :param jid:
|
603
|
+
# :return:
|
604
|
+
# """
|
605
|
+
return cls._from_user_or_none(user_store.get_by_jid(jid))
|
606
|
+
|
607
|
+
@classmethod
|
608
|
+
async def kill_by_jid(cls, jid: JID):
|
609
|
+
# """
|
610
|
+
# Terminate a user session.
|
611
|
+
#
|
612
|
+
# Meant to be called from :class:`BaseGateway` only.
|
613
|
+
#
|
614
|
+
# :param jid:
|
615
|
+
# :return:
|
616
|
+
# """
|
617
|
+
log.debug("Killing session of %s", jid)
|
618
|
+
for user, session in _sessions.items():
|
619
|
+
if user.jid == jid.bare:
|
620
|
+
break
|
621
|
+
else:
|
622
|
+
log.debug("Did not find a session for %s", jid)
|
623
|
+
return
|
624
|
+
for c in session.contacts:
|
625
|
+
c.unsubscribe()
|
626
|
+
await cls.xmpp.unregister(user)
|
627
|
+
del _sessions[user]
|
628
|
+
del user
|
629
|
+
del session
|
630
|
+
|
631
|
+
def __ack(self, msg: Message):
|
632
|
+
if not self.xmpp.PROPER_RECEIPTS:
|
633
|
+
self.xmpp.delivery_receipt.ack(msg)
|
634
|
+
|
635
|
+
def send_gateway_status(
|
636
|
+
self,
|
637
|
+
status: Optional[str] = None,
|
638
|
+
show=Optional[PresenceShows],
|
639
|
+
**kwargs,
|
640
|
+
):
|
478
641
|
"""
|
479
|
-
|
642
|
+
Send a presence from the gateway to the user.
|
643
|
+
|
644
|
+
Can be used to indicate the user session status, ie "SMS code required", "connected", …
|
480
645
|
|
481
|
-
|
646
|
+
:param status: A status message
|
647
|
+
:param show: Presence stanza 'show' element. I suggest using "dnd" to show
|
648
|
+
that the gateway is not fully functional
|
482
649
|
"""
|
483
|
-
|
650
|
+
self.__cached_presence = CachedPresence(status, show, kwargs)
|
651
|
+
self.xmpp.send_presence(
|
652
|
+
pto=self.user.bare_jid, pstatus=status, pshow=show, **kwargs
|
653
|
+
)
|
484
654
|
|
485
|
-
|
486
|
-
|
655
|
+
def send_cached_presence(self, to: JID):
|
656
|
+
if not self.__cached_presence:
|
657
|
+
self.xmpp.send_presence(pto=to, ptype="unavailable")
|
658
|
+
return
|
659
|
+
self.xmpp.send_presence(
|
660
|
+
pto=to,
|
661
|
+
pstatus=self.__cached_presence.status,
|
662
|
+
pshow=self.__cached_presence.show,
|
663
|
+
**self.__cached_presence.kwargs,
|
664
|
+
)
|
487
665
|
|
488
|
-
|
489
|
-
or :meth:`slidge.LegacyContact.send_file`
|
490
|
-
:param c:
|
666
|
+
def send_gateway_message(self, text: str, **msg_kwargs):
|
491
667
|
"""
|
492
|
-
|
668
|
+
Send a message from the gateway component to the user.
|
493
669
|
|
494
|
-
|
495
|
-
|
496
|
-
|
670
|
+
Can be used to indicate the user session status, ie "SMS code required", "connected", …
|
671
|
+
|
672
|
+
:param text: A text
|
497
673
|
"""
|
498
|
-
|
674
|
+
self.xmpp.send_text(text, mto=self.user.jid, **msg_kwargs)
|
499
675
|
|
500
|
-
|
501
|
-
|
676
|
+
def send_gateway_invite(
|
677
|
+
self,
|
678
|
+
muc: LegacyMUC,
|
679
|
+
reason: Optional[str] = None,
|
680
|
+
password: Optional[str] = None,
|
681
|
+
):
|
682
|
+
"""
|
683
|
+
Send an invitation to join a MUC, emanating from the gateway component.
|
502
684
|
|
503
|
-
:param
|
504
|
-
:param
|
505
|
-
:param
|
685
|
+
:param muc:
|
686
|
+
:param reason:
|
687
|
+
:param password:
|
506
688
|
"""
|
507
|
-
|
689
|
+
self.xmpp.invite_to(muc, reason=reason, password=password, mto=self.user.jid)
|
508
690
|
|
509
|
-
async def
|
691
|
+
async def input(self, text: str, **msg_kwargs):
|
510
692
|
"""
|
511
|
-
|
693
|
+
Request user input via direct messages from the gateway component.
|
512
694
|
|
513
|
-
|
695
|
+
Wraps call to :meth:`.BaseSession.input`
|
514
696
|
|
515
|
-
:param
|
516
|
-
|
697
|
+
:param text: The prompt to send to the user
|
698
|
+
:param msg_kwargs: Extra attributes
|
517
699
|
:return:
|
518
700
|
"""
|
519
|
-
|
701
|
+
return await self.xmpp.input(self.user.jid, text, **msg_kwargs)
|
520
702
|
|
521
|
-
async def
|
703
|
+
async def send_qr(self, text: str):
|
522
704
|
"""
|
523
|
-
|
705
|
+
Sends a QR code generated from 'text' via HTTP Upload and send the URL to
|
706
|
+
``self.user``
|
524
707
|
|
525
|
-
:param
|
526
|
-
:param emojis: Unicode characters representing reactions to the message ``legacy_msg_id``.
|
527
|
-
An empty string means "no reaction", ie, remove all reactions if any were present before
|
528
|
-
:param c: Contact the reaction refers to
|
708
|
+
:param text: Text to encode as a QR code
|
529
709
|
"""
|
530
|
-
|
710
|
+
await self.xmpp.send_qr(text, mto=self.user.jid)
|
531
711
|
|
532
|
-
|
533
|
-
|
534
|
-
|
712
|
+
def re_login(self):
|
713
|
+
# Logout then re-login
|
714
|
+
#
|
715
|
+
# No reason to override this
|
716
|
+
self.xmpp.re_login(self)
|
535
717
|
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
718
|
+
async def get_contact_or_group_or_participant(self, jid: JID):
|
719
|
+
if jid.bare in (contacts := self.contacts.known_contacts(only_friends=False)):
|
720
|
+
return contacts[jid.bare]
|
721
|
+
if jid.bare in (mucs := self.bookmarks._mucs_by_bare_jid):
|
722
|
+
return await self.__get_muc_or_participant(mucs[jid.bare], jid)
|
723
|
+
else:
|
724
|
+
muc = None
|
540
725
|
|
726
|
+
try:
|
727
|
+
return await self.contacts.by_jid(jid)
|
728
|
+
except XMPPError:
|
729
|
+
if muc is None:
|
730
|
+
try:
|
731
|
+
muc = await self.bookmarks.by_jid(jid)
|
732
|
+
except XMPPError:
|
733
|
+
return
|
734
|
+
return await self.__get_muc_or_participant(muc, jid)
|
735
|
+
|
736
|
+
@staticmethod
|
737
|
+
async def __get_muc_or_participant(muc: LegacyMUC, jid: JID):
|
738
|
+
if nick := jid.resource:
|
739
|
+
try:
|
740
|
+
return await muc.get_participant(
|
741
|
+
nick, raise_if_not_found=True, fill_first=True
|
742
|
+
)
|
743
|
+
except XMPPError:
|
744
|
+
return None
|
745
|
+
return muc
|
746
|
+
|
747
|
+
async def wait_for_ready(self, timeout: Optional[Union[int, float]] = 10):
|
748
|
+
# """
|
749
|
+
# Wait until session, contacts and bookmarks are ready
|
750
|
+
#
|
751
|
+
# (slidge internal use)
|
752
|
+
#
|
753
|
+
# :param timeout:
|
754
|
+
# :return:
|
755
|
+
# """
|
756
|
+
try:
|
757
|
+
await asyncio.wait_for(asyncio.shield(self.ready), timeout)
|
758
|
+
await asyncio.wait_for(asyncio.shield(self.contacts.ready), timeout)
|
759
|
+
await asyncio.wait_for(asyncio.shield(self.bookmarks.ready), timeout)
|
760
|
+
except asyncio.TimeoutError:
|
761
|
+
raise XMPPError(
|
762
|
+
"recipient-unavailable",
|
763
|
+
"Legacy session is not fully initialized, retry later",
|
764
|
+
)
|
541
765
|
|
542
|
-
SessionType = TypeVar("SessionType", bound=BaseSession)
|
543
766
|
|
544
767
|
_sessions: dict[GatewayUser, BaseSession] = {}
|
545
768
|
log = logging.getLogger(__name__)
|