slidge 0.2.12__py3-none-any.whl → 0.3.0__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/__init__.py +5 -2
- slidge/command/adhoc.py +9 -3
- slidge/command/admin.py +16 -12
- slidge/command/base.py +16 -12
- slidge/command/chat_command.py +25 -16
- slidge/command/user.py +7 -8
- slidge/contact/contact.py +123 -210
- slidge/contact/roster.py +108 -105
- slidge/core/config.py +2 -43
- slidge/core/dispatcher/caps.py +9 -2
- slidge/core/dispatcher/disco.py +13 -3
- slidge/core/dispatcher/message/__init__.py +1 -1
- slidge/core/dispatcher/message/chat_state.py +17 -8
- slidge/core/dispatcher/message/marker.py +7 -5
- slidge/core/dispatcher/message/message.py +120 -93
- slidge/core/dispatcher/muc/__init__.py +1 -1
- slidge/core/dispatcher/muc/admin.py +4 -4
- slidge/core/dispatcher/muc/mam.py +10 -6
- slidge/core/dispatcher/muc/misc.py +4 -2
- slidge/core/dispatcher/muc/owner.py +5 -3
- slidge/core/dispatcher/muc/ping.py +3 -1
- slidge/core/dispatcher/presence.py +26 -15
- slidge/core/dispatcher/registration.py +20 -12
- slidge/core/dispatcher/search.py +7 -3
- slidge/core/dispatcher/session_dispatcher.py +13 -5
- slidge/core/dispatcher/util.py +37 -27
- slidge/core/dispatcher/vcard.py +7 -4
- slidge/core/gateway.py +177 -87
- slidge/core/mixins/__init__.py +1 -11
- slidge/core/mixins/attachment.py +200 -147
- slidge/core/mixins/avatar.py +105 -177
- slidge/core/mixins/base.py +3 -1
- slidge/core/mixins/db.py +50 -2
- slidge/core/mixins/disco.py +1 -1
- slidge/core/mixins/message.py +19 -17
- slidge/core/mixins/message_maker.py +29 -15
- slidge/core/mixins/message_text.py +67 -30
- slidge/core/mixins/presence.py +94 -37
- slidge/core/pubsub.py +42 -47
- slidge/core/session.py +95 -60
- slidge/db/alembic/versions/cef02a8b1451_initial_schema.py +361 -0
- slidge/db/avatar.py +150 -119
- slidge/db/meta.py +33 -22
- slidge/db/models.py +69 -117
- slidge/db/store.py +414 -1094
- slidge/group/archive.py +65 -55
- slidge/group/bookmarks.py +96 -59
- slidge/group/participant.py +150 -144
- slidge/group/room.py +345 -327
- slidge/main.py +34 -22
- slidge/migration.py +17 -29
- slidge/slixfix/__init__.py +20 -4
- slidge/slixfix/delivery_receipt.py +6 -4
- slidge/slixfix/link_preview/link_preview.py +1 -1
- slidge/slixfix/link_preview/stanza.py +1 -1
- slidge/slixfix/roster.py +5 -7
- slidge/slixfix/xep_0077/register.py +8 -8
- slidge/slixfix/xep_0077/stanza.py +7 -7
- slidge/slixfix/xep_0100/gateway.py +12 -13
- slidge/slixfix/xep_0153/vcard_avatar.py +1 -1
- slidge/slixfix/xep_0292/vcard4.py +12 -2
- slidge/util/archive_msg.py +11 -5
- slidge/util/conf.py +27 -21
- slidge/util/jid_escaping.py +1 -1
- slidge/{core/mixins → util}/lock.py +6 -6
- slidge/util/test.py +30 -29
- slidge/util/types.py +24 -18
- slidge/util/util.py +26 -22
- {slidge-0.2.12.dist-info → slidge-0.3.0.dist-info}/METADATA +1 -1
- slidge-0.3.0.dist-info/RECORD +95 -0
- {slidge-0.2.12.dist-info → slidge-0.3.0.dist-info}/WHEEL +1 -1
- slidge/db/alembic/versions/04cf35e3cf85_add_participant_nickname_no_illegal.py +0 -33
- slidge/db/alembic/versions/09f27f098baa_add_missing_attributes_in_room.py +0 -36
- slidge/db/alembic/versions/15b0bd83407a_remove_bogus_unique_constraints_on_room_.py +0 -85
- slidge/db/alembic/versions/2461390c0af2_store_contacts_caps_verstring_in_db.py +0 -36
- slidge/db/alembic/versions/29f5280c61aa_store_subject_setter_in_room.py +0 -37
- slidge/db/alembic/versions/2b1f45ab7379_store_room_subject_setter_by_nickname.py +0 -41
- slidge/db/alembic/versions/3071e0fa69d4_add_contact_client_type.py +0 -52
- slidge/db/alembic/versions/45c24cc73c91_add_bob.py +0 -42
- slidge/db/alembic/versions/5bd48bfdffa2_lift_room_legacy_id_constraint.py +0 -61
- slidge/db/alembic/versions/82a4af84b679_add_muc_history_filled.py +0 -48
- slidge/db/alembic/versions/8b993243a536_add_vcard_content_to_contact_table.py +0 -43
- slidge/db/alembic/versions/8d2ced764698_rely_on_db_to_store_contacts_rooms_and_.py +0 -139
- slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +0 -50
- slidge/db/alembic/versions/abba1ae0edb3_store_avatar_legacy_id_in_the_contact_.py +0 -79
- slidge/db/alembic/versions/b33993e87db3_move_everything_to_persistent_db.py +0 -214
- slidge/db/alembic/versions/b64b1a793483_add_source_and_legacy_id_for_archived_.py +0 -52
- slidge/db/alembic/versions/c4a8ec35a0e8_per_room_user_nick.py +0 -34
- slidge/db/alembic/versions/e91195719c2c_store_users_avatars_persistently.py +0 -26
- slidge-0.2.12.dist-info/RECORD +0 -112
- {slidge-0.2.12.dist-info → slidge-0.3.0.dist-info}/entry_points.txt +0 -0
- {slidge-0.2.12.dist-info → slidge-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {slidge-0.2.12.dist-info → slidge-0.3.0.dist-info}/top_level.txt +0 -0
@@ -2,6 +2,9 @@ import logging
|
|
2
2
|
from datetime import datetime
|
3
3
|
from typing import Iterable, Optional
|
4
4
|
|
5
|
+
from slixmpp import Message
|
6
|
+
|
7
|
+
from ...util.archive_msg import HistoryMessage
|
5
8
|
from ...util.types import (
|
6
9
|
LegacyMessageType,
|
7
10
|
LegacyThreadType,
|
@@ -9,6 +12,7 @@ from ...util.types import (
|
|
9
12
|
MessageReference,
|
10
13
|
ProcessingHint,
|
11
14
|
)
|
15
|
+
from ...util.util import add_quote_prefix
|
12
16
|
from .message_maker import MessageMaker
|
13
17
|
|
14
18
|
|
@@ -23,9 +27,13 @@ class TextMessageMixin(MessageMaker):
|
|
23
27
|
|
24
28
|
def _replace_id(self, legacy_msg_id: LegacyMessageType):
|
25
29
|
if self.mtype == "groupchat":
|
26
|
-
|
27
|
-
self.
|
28
|
-
|
30
|
+
with self.xmpp.store.session() as orm:
|
31
|
+
ids = self.xmpp.store.id_map.get_xmpp(
|
32
|
+
orm, self._recipient_pk(), str(legacy_msg_id), True
|
33
|
+
)
|
34
|
+
if ids:
|
35
|
+
return ids[0]
|
36
|
+
return self.session.legacy_to_xmpp_msg_id(legacy_msg_id)
|
29
37
|
else:
|
30
38
|
return self._legacy_to_xmpp(legacy_msg_id)
|
31
39
|
|
@@ -38,9 +46,9 @@ class TextMessageMixin(MessageMaker):
|
|
38
46
|
reply_to: Optional[MessageReference] = None,
|
39
47
|
thread: Optional[LegacyThreadType] = None,
|
40
48
|
hints: Optional[Iterable[ProcessingHint]] = None,
|
41
|
-
carbon=False,
|
42
|
-
archive_only=False,
|
43
|
-
correction=False,
|
49
|
+
carbon: bool = False,
|
50
|
+
archive_only: bool = False,
|
51
|
+
correction: bool = False,
|
44
52
|
correction_event_id: Optional[LegacyMessageType] = None,
|
45
53
|
link_previews: Optional[list[LinkPreview]] = None,
|
46
54
|
**send_kwargs,
|
@@ -69,24 +77,28 @@ class TextMessageMixin(MessageMaker):
|
|
69
77
|
but store it in the archive. Meant to be used during ``MUC.backfill()``
|
70
78
|
"""
|
71
79
|
if carbon and not hasattr(self, "muc"):
|
72
|
-
|
73
|
-
self.
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
80
|
+
with self.xmpp.store.session() as orm:
|
81
|
+
if not correction and self.xmpp.store.id_map.was_sent_by_user(
|
82
|
+
orm, self._recipient_pk(), str(legacy_msg_id), self.is_group
|
83
|
+
):
|
84
|
+
log.warning(
|
85
|
+
"Carbon message for a message an XMPP has sent? This is a bug! %s",
|
86
|
+
legacy_msg_id,
|
87
|
+
)
|
88
|
+
return
|
89
|
+
if hasattr(self, "muc") and not self.is_user: # type:ignore
|
90
|
+
log.warning(
|
91
|
+
"send_text() called with carbon=True on a participant who is not the user",
|
92
|
+
legacy_msg_id,
|
93
|
+
)
|
94
|
+
self.xmpp.store.id_map.set_msg(
|
95
|
+
orm,
|
96
|
+
self._recipient_pk(),
|
97
|
+
str(legacy_msg_id),
|
98
|
+
[self.session.legacy_to_xmpp_msg_id(legacy_msg_id)],
|
99
|
+
self.is_group,
|
84
100
|
)
|
85
|
-
|
86
|
-
self.session.user_pk,
|
87
|
-
str(legacy_msg_id),
|
88
|
-
self.session.legacy_to_xmpp_msg_id(legacy_msg_id),
|
89
|
-
)
|
101
|
+
orm.commit()
|
90
102
|
hints = self.__default_hints(hints)
|
91
103
|
msg = self._make_message(
|
92
104
|
mbody=body,
|
@@ -117,12 +129,12 @@ class TextMessageMixin(MessageMaker):
|
|
117
129
|
reply_to: Optional[MessageReference] = None,
|
118
130
|
thread: Optional[LegacyThreadType] = None,
|
119
131
|
hints: Optional[Iterable[ProcessingHint]] = None,
|
120
|
-
carbon=False,
|
121
|
-
archive_only=False,
|
132
|
+
carbon: bool = False,
|
133
|
+
archive_only: bool = False,
|
122
134
|
correction_event_id: Optional[LegacyMessageType] = None,
|
123
135
|
link_previews: Optional[list[LinkPreview]] = None,
|
124
136
|
**send_kwargs,
|
125
|
-
):
|
137
|
+
) -> None:
|
126
138
|
"""
|
127
139
|
Modify a message that was previously sent by this :term:`XMPP Entity`.
|
128
140
|
|
@@ -165,7 +177,7 @@ class TextMessageMixin(MessageMaker):
|
|
165
177
|
emojis: Iterable[str] = (),
|
166
178
|
thread: Optional[LegacyThreadType] = None,
|
167
179
|
**kwargs,
|
168
|
-
):
|
180
|
+
) -> None:
|
169
181
|
"""
|
170
182
|
Send a reaction (:xep:`0444`) from this :term:`XMPP Entity`.
|
171
183
|
|
@@ -174,20 +186,45 @@ class TextMessageMixin(MessageMaker):
|
|
174
186
|
:param thread:
|
175
187
|
"""
|
176
188
|
msg = self._make_message(
|
177
|
-
hints={"store"}, carbon=kwargs.get("carbon"), thread=thread
|
189
|
+
hints={"store"}, carbon=bool(kwargs.get("carbon")), thread=thread
|
178
190
|
)
|
179
191
|
xmpp_id = kwargs.pop("xmpp_id", None)
|
180
192
|
if not xmpp_id:
|
181
193
|
xmpp_id = self._legacy_to_xmpp(legacy_msg_id)
|
182
194
|
self.xmpp["xep_0444"].set_reactions(msg, to_id=xmpp_id, reactions=emojis)
|
195
|
+
self.__add_reaction_fallback(msg, legacy_msg_id, emojis)
|
183
196
|
self._send(msg, **kwargs)
|
184
197
|
|
198
|
+
def __add_reaction_fallback(
|
199
|
+
self,
|
200
|
+
msg: Message,
|
201
|
+
legacy_msg_id: LegacyMessageType,
|
202
|
+
emojis: Iterable[str] = (),
|
203
|
+
) -> None:
|
204
|
+
if not self.session.user.preferences.get("reaction_fallback", False):
|
205
|
+
return
|
206
|
+
msg["fallback"]["for"] = self.xmpp.plugin["xep_0444"].namespace
|
207
|
+
msg["fallback"].enable("body")
|
208
|
+
msg["body"] = " ".join(emojis)
|
209
|
+
if not self.is_participant:
|
210
|
+
return
|
211
|
+
with self.xmpp.store.session() as orm:
|
212
|
+
archived = self.xmpp.store.mam.get_by_legacy_id(
|
213
|
+
orm, self.muc.stored.id, str(legacy_msg_id)
|
214
|
+
)
|
215
|
+
if archived is None:
|
216
|
+
return
|
217
|
+
history_msg = HistoryMessage(archived.stanza)
|
218
|
+
msg["body"] = (
|
219
|
+
add_quote_prefix(history_msg.stanza["body"]) + "\n" + msg["body"]
|
220
|
+
)
|
221
|
+
|
185
222
|
def retract(
|
186
223
|
self,
|
187
224
|
legacy_msg_id: LegacyMessageType,
|
188
225
|
thread: Optional[LegacyThreadType] = None,
|
189
226
|
**kwargs,
|
190
|
-
):
|
227
|
+
) -> None:
|
191
228
|
"""
|
192
229
|
Send a message retraction (:XEP:`0424`) from this :term:`XMPP Entity`.
|
193
230
|
|
@@ -198,7 +235,7 @@ class TextMessageMixin(MessageMaker):
|
|
198
235
|
state=None,
|
199
236
|
hints={"store"},
|
200
237
|
mbody=f"/me retracted the message {legacy_msg_id}",
|
201
|
-
carbon=kwargs.get("carbon"),
|
238
|
+
carbon=bool(kwargs.get("carbon")),
|
202
239
|
thread=thread,
|
203
240
|
)
|
204
241
|
msg.enable("fallback")
|
slidge/core/mixins/presence.py
CHANGED
@@ -1,13 +1,20 @@
|
|
1
1
|
import re
|
2
2
|
from asyncio import Task, sleep
|
3
3
|
from datetime import datetime, timedelta, timezone
|
4
|
-
from
|
4
|
+
from functools import partial
|
5
|
+
from typing import TYPE_CHECKING, Optional
|
5
6
|
|
6
7
|
from slixmpp.types import PresenceShows, PresenceTypes
|
8
|
+
from sqlalchemy.exc import InvalidRequestError
|
9
|
+
from sqlalchemy.orm.exc import DetachedInstanceError
|
7
10
|
|
11
|
+
from ...db.models import Contact, Participant
|
8
12
|
from ...util.types import CachedPresence
|
9
|
-
from .. import config
|
10
13
|
from .base import BaseSender
|
14
|
+
from .db import DBMixin
|
15
|
+
|
16
|
+
if TYPE_CHECKING:
|
17
|
+
from ..session import BaseSession
|
11
18
|
|
12
19
|
|
13
20
|
class _NoChange(Exception):
|
@@ -15,43 +22,85 @@ class _NoChange(Exception):
|
|
15
22
|
|
16
23
|
|
17
24
|
_FRIEND_REQUEST_PRESENCES = {"subscribe", "unsubscribe", "subscribed", "unsubscribed"}
|
25
|
+
_UPDATE_LAST_SEEN_FALLBACK_TASKS = dict[int, Task]()
|
26
|
+
_ONE_WEEK_SECONDS = 3600 * 24 * 7
|
27
|
+
|
28
|
+
|
29
|
+
async def _update_last_seen_fallback(session: "BaseSession", contact_pk: int) -> None:
|
30
|
+
await sleep(_ONE_WEEK_SECONDS)
|
31
|
+
with session.xmpp.store.session() as orm:
|
32
|
+
stored = orm.get(Contact, contact_pk)
|
33
|
+
if stored is None:
|
34
|
+
return
|
35
|
+
contact = session.contacts.from_store(stored)
|
36
|
+
contact.send_last_presence(force=True, no_cache_online=False)
|
18
37
|
|
19
38
|
|
20
|
-
|
39
|
+
def _clear_last_seen_task(contact_pk: int, _task) -> None:
|
40
|
+
try:
|
41
|
+
del _UPDATE_LAST_SEEN_FALLBACK_TASKS[contact_pk]
|
42
|
+
except KeyError:
|
43
|
+
pass
|
44
|
+
|
45
|
+
|
46
|
+
class PresenceMixin(BaseSender, DBMixin):
|
21
47
|
_ONLY_SEND_PRESENCE_CHANGES = False
|
22
|
-
contact_pk: Optional[int] = None
|
23
48
|
|
24
|
-
|
49
|
+
stored: Contact | Participant
|
50
|
+
|
51
|
+
def __init__(self, *a, **k) -> None:
|
25
52
|
super().__init__(*a, **k)
|
26
|
-
# FIXME: this should not be an attribute of this mixin to allow garbage
|
27
|
-
# collection of instances
|
28
|
-
self.__update_last_seen_fallback_task: Optional[Task] = None
|
29
53
|
# this is only used when a presence is set during Contact.update_info(),
|
30
54
|
# when the contact does not have a DB primary key yet, and is written
|
31
55
|
# to DB at the end of update_info()
|
32
56
|
self.cached_presence: Optional[CachedPresence] = None
|
33
57
|
|
34
|
-
|
35
|
-
|
36
|
-
|
58
|
+
def __stored(self) -> Contact | None:
|
59
|
+
if isinstance(self.stored, Contact):
|
60
|
+
return self.stored
|
61
|
+
else:
|
62
|
+
try:
|
63
|
+
return self.stored.contact
|
64
|
+
except DetachedInstanceError:
|
65
|
+
with self.xmpp.store.session() as orm:
|
66
|
+
orm.add(self.stored)
|
67
|
+
return self.stored.contact
|
68
|
+
|
69
|
+
@property
|
70
|
+
def __contact_pk(self) -> int | None:
|
71
|
+
stored = self.__stored()
|
72
|
+
return None if stored is None else stored.id
|
37
73
|
|
38
74
|
def _get_last_presence(self) -> Optional[CachedPresence]:
|
39
|
-
|
75
|
+
stored = self.__stored()
|
76
|
+
if stored is None or not stored.cached_presence:
|
40
77
|
return None
|
41
|
-
return
|
78
|
+
return CachedPresence(
|
79
|
+
None
|
80
|
+
if stored.last_seen is None
|
81
|
+
else stored.last_seen.replace(tzinfo=timezone.utc),
|
82
|
+
stored.ptype, # type:ignore
|
83
|
+
stored.pstatus,
|
84
|
+
stored.pshow, # type:ignore
|
85
|
+
)
|
42
86
|
|
43
|
-
def _store_last_presence(self, new: CachedPresence):
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
87
|
+
def _store_last_presence(self, new: CachedPresence) -> None:
|
88
|
+
stored = self.__stored()
|
89
|
+
if stored is not None:
|
90
|
+
stored.cached_presence = True
|
91
|
+
for k, v in new._asdict().items():
|
92
|
+
setattr(stored, k, v)
|
93
|
+
try:
|
94
|
+
self.commit()
|
95
|
+
except InvalidRequestError:
|
96
|
+
self.commit(merge=True)
|
48
97
|
|
49
98
|
def _make_presence(
|
50
99
|
self,
|
51
100
|
*,
|
52
101
|
last_seen: Optional[datetime] = None,
|
53
|
-
force=False,
|
54
|
-
bare=False,
|
102
|
+
force: bool = False,
|
103
|
+
bare: bool = False,
|
55
104
|
ptype: Optional[PresenceTypes] = None,
|
56
105
|
pstatus: Optional[str] = None,
|
57
106
|
pshow: Optional[PresenceShows] = None,
|
@@ -67,8 +116,10 @@ class PresenceMixin(BaseSender):
|
|
67
116
|
)
|
68
117
|
if old != new:
|
69
118
|
if hasattr(self, "muc") and ptype == "unavailable":
|
70
|
-
|
71
|
-
|
119
|
+
stored = self.__stored()
|
120
|
+
if stored is not None:
|
121
|
+
stored.cached_presence = False
|
122
|
+
self.commit(merge=True)
|
72
123
|
else:
|
73
124
|
self._store_last_presence(new)
|
74
125
|
if old and not force and self._ONLY_SEND_PRESENCE_CHANGES:
|
@@ -87,27 +138,33 @@ class PresenceMixin(BaseSender):
|
|
87
138
|
)
|
88
139
|
if last_seen:
|
89
140
|
# it's ugly to check for the presence of this string, but a better fix is more work
|
90
|
-
if
|
141
|
+
if not re.match(
|
91
142
|
".*Last seen .*", p["status"]
|
92
|
-
):
|
143
|
+
) and self.session.user.preferences.get("last_seen_fallback", True):
|
93
144
|
last_seen_fallback, recent = get_last_seen_fallback(last_seen)
|
94
145
|
if p["status"]:
|
95
146
|
p["status"] = p["status"] + " -- " + last_seen_fallback
|
96
147
|
else:
|
97
148
|
p["status"] = last_seen_fallback
|
98
|
-
|
149
|
+
pk = self.__contact_pk
|
150
|
+
if recent and pk is not None:
|
99
151
|
# if less than a week, we use sth like 'Last seen: Monday, 8:05",
|
100
152
|
# but if lasts more than a week, this is not very informative, so
|
101
153
|
# we need to force resend an updated presence status
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
154
|
+
task = _UPDATE_LAST_SEEN_FALLBACK_TASKS.get(pk)
|
155
|
+
if task is not None:
|
156
|
+
task.cancel()
|
157
|
+
task = self.session.create_task(
|
158
|
+
_update_last_seen_fallback(self.session, pk)
|
106
159
|
)
|
160
|
+
_UPDATE_LAST_SEEN_FALLBACK_TASKS[pk] = task
|
161
|
+
task.add_done_callback(partial(_clear_last_seen_task, pk))
|
107
162
|
p["idle"]["since"] = last_seen
|
108
163
|
return p
|
109
164
|
|
110
|
-
def send_last_presence(
|
165
|
+
def send_last_presence(
|
166
|
+
self, force: bool = False, no_cache_online: bool = False
|
167
|
+
) -> None:
|
111
168
|
if (cache := self._get_last_presence()) is None:
|
112
169
|
if force:
|
113
170
|
if no_cache_online:
|
@@ -129,7 +186,7 @@ class PresenceMixin(BaseSender):
|
|
129
186
|
self,
|
130
187
|
status: Optional[str] = None,
|
131
188
|
last_seen: Optional[datetime] = None,
|
132
|
-
):
|
189
|
+
) -> None:
|
133
190
|
"""
|
134
191
|
Send an "online" presence from this contact to the user.
|
135
192
|
|
@@ -145,7 +202,7 @@ class PresenceMixin(BaseSender):
|
|
145
202
|
self,
|
146
203
|
status: Optional[str] = None,
|
147
204
|
last_seen: Optional[datetime] = None,
|
148
|
-
):
|
205
|
+
) -> None:
|
149
206
|
"""
|
150
207
|
Send an "away" presence from this contact to the user.
|
151
208
|
|
@@ -166,7 +223,7 @@ class PresenceMixin(BaseSender):
|
|
166
223
|
self,
|
167
224
|
status: Optional[str] = None,
|
168
225
|
last_seen: Optional[datetime] = None,
|
169
|
-
):
|
226
|
+
) -> None:
|
170
227
|
"""
|
171
228
|
Send an "extended away" presence from this contact to the user.
|
172
229
|
|
@@ -187,7 +244,7 @@ class PresenceMixin(BaseSender):
|
|
187
244
|
self,
|
188
245
|
status: Optional[str] = None,
|
189
246
|
last_seen: Optional[datetime] = None,
|
190
|
-
):
|
247
|
+
) -> None:
|
191
248
|
"""
|
192
249
|
Send a "busy" (ie, "dnd") presence from this contact to the user,
|
193
250
|
|
@@ -205,7 +262,7 @@ class PresenceMixin(BaseSender):
|
|
205
262
|
self,
|
206
263
|
status: Optional[str] = None,
|
207
264
|
last_seen: Optional[datetime] = None,
|
208
|
-
):
|
265
|
+
) -> None:
|
209
266
|
"""
|
210
267
|
Send an "offline" presence from this contact to the user.
|
211
268
|
|
@@ -222,9 +279,9 @@ class PresenceMixin(BaseSender):
|
|
222
279
|
pass
|
223
280
|
|
224
281
|
|
225
|
-
def get_last_seen_fallback(last_seen: datetime):
|
282
|
+
def get_last_seen_fallback(last_seen: datetime) -> tuple[str, bool]:
|
226
283
|
now = datetime.now(tz=timezone.utc)
|
227
284
|
if now - last_seen < timedelta(days=7):
|
228
|
-
return f"Last seen {last_seen:%A %H:%M GMT}", True
|
285
|
+
return f"Last seen {last_seen:%A %H:%M %p GMT}", True
|
229
286
|
else:
|
230
287
|
return f"Last seen {last_seen:%b %-d %Y}", False
|
slidge/core/pubsub.py
CHANGED
@@ -21,8 +21,8 @@ from slixmpp.plugins.xep_0292.stanza import VCard4
|
|
21
21
|
from slixmpp.types import JidStr, OptJidStr
|
22
22
|
|
23
23
|
from ..db.avatar import CachedAvatar, avatar_cache
|
24
|
-
from ..db.
|
25
|
-
from .
|
24
|
+
from ..db.models import GatewayUser
|
25
|
+
from ..util.lock import NamedLockMixin
|
26
26
|
|
27
27
|
if TYPE_CHECKING:
|
28
28
|
from slidge.core.gateway import BaseGateway
|
@@ -32,14 +32,8 @@ if TYPE_CHECKING:
|
|
32
32
|
VCARD4_NAMESPACE = "urn:xmpp:vcard4"
|
33
33
|
|
34
34
|
|
35
|
-
class
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
class PepAvatar(PepItem):
|
40
|
-
store: SlidgeStore
|
41
|
-
|
42
|
-
def __init__(self):
|
35
|
+
class PepAvatar:
|
36
|
+
def __init__(self) -> None:
|
43
37
|
self.metadata: Optional[AvatarMetadata] = None
|
44
38
|
self.id: Optional[str] = None
|
45
39
|
self._avatar_data_path: Optional[Path] = None
|
@@ -52,7 +46,7 @@ class PepAvatar(PepItem):
|
|
52
46
|
data.set_value(self._avatar_data_path.read_bytes())
|
53
47
|
return data
|
54
48
|
|
55
|
-
def set_avatar_from_cache(self, cached_avatar: CachedAvatar):
|
49
|
+
def set_avatar_from_cache(self, cached_avatar: CachedAvatar) -> None:
|
56
50
|
metadata = AvatarMetadata()
|
57
51
|
self.id = cached_avatar.hash
|
58
52
|
metadata.add_info(
|
@@ -66,17 +60,6 @@ class PepAvatar(PepItem):
|
|
66
60
|
self._avatar_data_path = cached_avatar.path
|
67
61
|
|
68
62
|
|
69
|
-
class PepNick(PepItem):
|
70
|
-
contact_store: ContactStore
|
71
|
-
|
72
|
-
def __init__(self, nick: Optional[str] = None):
|
73
|
-
nickname = UserNick()
|
74
|
-
if nick is not None:
|
75
|
-
nickname["nick"] = nick
|
76
|
-
self.nick = nickname
|
77
|
-
self.__nick_str = nick
|
78
|
-
|
79
|
-
|
80
63
|
class PubSubComponent(NamedLockMixin, BasePlugin):
|
81
64
|
xmpp: "BaseGateway"
|
82
65
|
|
@@ -91,11 +74,11 @@ class PubSubComponent(NamedLockMixin, BasePlugin):
|
|
91
74
|
default_config = {"component_name": None}
|
92
75
|
component_name: str
|
93
76
|
|
94
|
-
def __init__(self, *a, **kw):
|
77
|
+
def __init__(self, *a, **kw) -> None:
|
95
78
|
super(PubSubComponent, self).__init__(*a, **kw)
|
96
79
|
register_stanza_plugin(EventItem, UserNick)
|
97
80
|
|
98
|
-
def plugin_init(self):
|
81
|
+
def plugin_init(self) -> None:
|
99
82
|
self.xmpp.register_handler(
|
100
83
|
CoroutineCallback(
|
101
84
|
"pubsub_get_avatar_data",
|
@@ -144,7 +127,7 @@ class PubSubComponent(NamedLockMixin, BasePlugin):
|
|
144
127
|
|
145
128
|
async def on_presence_available(
|
146
129
|
self, p: Presence, contact: Optional["LegacyContact"]
|
147
|
-
):
|
130
|
+
) -> None:
|
148
131
|
if p.get_plugin("muc_join", check=True) is not None:
|
149
132
|
log.debug("Ignoring MUC presence here")
|
150
133
|
return
|
@@ -177,14 +160,16 @@ class PubSubComponent(NamedLockMixin, BasePlugin):
|
|
177
160
|
except XMPPError:
|
178
161
|
pass
|
179
162
|
else:
|
180
|
-
await self.__broadcast(data=pep_nick
|
163
|
+
await self.__broadcast(data=pep_nick, from_=p.get_to(), to=from_)
|
181
164
|
|
182
165
|
if contact is not None and VCARD4_NAMESPACE + "+notify" in features:
|
183
166
|
await self.broadcast_vcard_event(
|
184
167
|
p.get_to(), from_, await contact.get_vcard()
|
185
168
|
)
|
186
169
|
|
187
|
-
async def broadcast_vcard_event(
|
170
|
+
async def broadcast_vcard_event(
|
171
|
+
self, from_: JID, to: JID, vcard: VCard4 | None
|
172
|
+
) -> None:
|
188
173
|
item = Item()
|
189
174
|
item.namespace = VCARD4_NAMESPACE
|
190
175
|
item["id"] = "current"
|
@@ -211,33 +196,33 @@ class PubSubComponent(NamedLockMixin, BasePlugin):
|
|
211
196
|
) -> PepAvatar:
|
212
197
|
if stanza.get_to() == self.xmpp.boundjid.bare:
|
213
198
|
item = PepAvatar()
|
214
|
-
if
|
215
|
-
item.set_avatar_from_cache(
|
199
|
+
if self.xmpp.avatar is not None:
|
200
|
+
item.set_avatar_from_cache(self.xmpp.avatar)
|
216
201
|
return item
|
217
202
|
|
218
203
|
if contact is None:
|
219
204
|
contact = await self.__get_contact(stanza)
|
220
205
|
|
221
206
|
item = PepAvatar()
|
222
|
-
if contact.
|
223
|
-
stored = avatar_cache.
|
207
|
+
if contact.stored.avatar is not None:
|
208
|
+
stored = avatar_cache.get(contact.stored.avatar)
|
224
209
|
assert stored is not None
|
225
210
|
item.set_avatar_from_cache(stored)
|
226
211
|
return item
|
227
212
|
|
228
213
|
async def _get_authorized_nick(
|
229
214
|
self, stanza: Union[Iq, Presence], contact: Optional["LegacyContact"] = None
|
230
|
-
) ->
|
215
|
+
) -> UserNick:
|
231
216
|
if stanza.get_to() == self.xmpp.boundjid.bare:
|
232
|
-
return
|
217
|
+
return get_user_nick(self.xmpp.COMPONENT_NAME)
|
233
218
|
|
234
219
|
if contact is None:
|
235
220
|
contact = await self.__get_contact(stanza)
|
236
221
|
|
237
222
|
if contact.name is not None:
|
238
|
-
return
|
223
|
+
return get_user_nick(contact.name)
|
239
224
|
else:
|
240
|
-
return
|
225
|
+
return UserNick()
|
241
226
|
|
242
227
|
def __reply_with(
|
243
228
|
self, iq: Iq, content: AvatarData | AvatarMetadata | None, item_id: str | None
|
@@ -254,11 +239,11 @@ class PubSubComponent(NamedLockMixin, BasePlugin):
|
|
254
239
|
else:
|
255
240
|
raise XMPPError("item-not-found")
|
256
241
|
|
257
|
-
async def _get_avatar_data(self, iq: Iq):
|
242
|
+
async def _get_avatar_data(self, iq: Iq) -> None:
|
258
243
|
pep_avatar = await self._get_authorized_avatar(iq)
|
259
244
|
self.__reply_with(iq, pep_avatar.data, pep_avatar.id)
|
260
245
|
|
261
|
-
async def _get_avatar_metadata(self, iq: Iq):
|
246
|
+
async def _get_avatar_metadata(self, iq: Iq) -> None:
|
262
247
|
pep_avatar = await self._get_authorized_avatar(iq)
|
263
248
|
self.__reply_with(iq, pep_avatar.metadata, pep_avatar.id)
|
264
249
|
|
@@ -279,7 +264,7 @@ class PubSubComponent(NamedLockMixin, BasePlugin):
|
|
279
264
|
payload: Optional[Union[AvatarMetadata, AvatarData, VCard4]],
|
280
265
|
id_: Optional[str],
|
281
266
|
namespace: Optional[str] = None,
|
282
|
-
):
|
267
|
+
) -> None:
|
283
268
|
result = iq.reply()
|
284
269
|
item = Item()
|
285
270
|
if payload:
|
@@ -291,7 +276,9 @@ class PubSubComponent(NamedLockMixin, BasePlugin):
|
|
291
276
|
result["pubsub"]["items"].append(item)
|
292
277
|
result.send()
|
293
278
|
|
294
|
-
async def __broadcast(
|
279
|
+
async def __broadcast(
|
280
|
+
self, data, from_: JidStr, to: OptJidStr = None, **kwargs
|
281
|
+
) -> None:
|
295
282
|
from_ = JID(from_)
|
296
283
|
if from_ != self.xmpp.boundjid.bare and to is not None:
|
297
284
|
to = JID(to)
|
@@ -319,10 +306,11 @@ class PubSubComponent(NamedLockMixin, BasePlugin):
|
|
319
306
|
msg.append(event)
|
320
307
|
|
321
308
|
if to is None:
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
309
|
+
with self.xmpp.store.session() as orm:
|
310
|
+
for u in orm.query(GatewayUser).all():
|
311
|
+
new_msg = copy(msg)
|
312
|
+
new_msg.set_to(u.jid.bare)
|
313
|
+
new_msg.send()
|
326
314
|
else:
|
327
315
|
msg.set_to(to)
|
328
316
|
msg.send()
|
@@ -345,11 +333,18 @@ class PubSubComponent(NamedLockMixin, BasePlugin):
|
|
345
333
|
user_jid: JID,
|
346
334
|
jid: JidStr,
|
347
335
|
nick: Optional[str] = None,
|
348
|
-
):
|
336
|
+
) -> None:
|
349
337
|
jid = JID(jid)
|
350
|
-
nickname =
|
351
|
-
log.debug("New nickname: %s", nickname
|
352
|
-
self.xmpp.loop.create_task(self.__broadcast(nickname
|
338
|
+
nickname = get_user_nick(nick)
|
339
|
+
log.debug("New nickname: %s", nickname)
|
340
|
+
self.xmpp.loop.create_task(self.__broadcast(nickname, jid, user_jid.bare))
|
341
|
+
|
342
|
+
|
343
|
+
def get_user_nick(nick: Optional[str] = None) -> UserNick:
|
344
|
+
user_nick = UserNick()
|
345
|
+
if nick is not None:
|
346
|
+
user_nick["nick"] = nick
|
347
|
+
return user_nick
|
353
348
|
|
354
349
|
|
355
350
|
log = logging.getLogger(__name__)
|