slidge 0.1.3__py3-none-any.whl → 0.2.0a1__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 +3 -5
- slidge/__main__.py +2 -196
- slidge/__version__.py +5 -0
- slidge/command/adhoc.py +8 -1
- slidge/command/admin.py +6 -7
- slidge/command/base.py +1 -2
- slidge/command/register.py +32 -16
- slidge/command/user.py +85 -6
- slidge/contact/contact.py +165 -49
- slidge/contact/roster.py +122 -47
- slidge/core/config.py +14 -11
- slidge/core/gateway/base.py +148 -36
- slidge/core/gateway/caps.py +7 -5
- slidge/core/gateway/disco.py +2 -4
- slidge/core/gateway/mam.py +1 -4
- slidge/core/gateway/muc_admin.py +1 -1
- slidge/core/gateway/ping.py +2 -3
- slidge/core/gateway/presence.py +1 -1
- slidge/core/gateway/registration.py +32 -21
- slidge/core/gateway/search.py +3 -5
- slidge/core/gateway/session_dispatcher.py +120 -57
- slidge/core/gateway/vcard_temp.py +7 -5
- slidge/core/mixins/__init__.py +11 -1
- slidge/core/mixins/attachment.py +32 -14
- slidge/core/mixins/avatar.py +90 -25
- slidge/core/mixins/base.py +8 -2
- slidge/core/mixins/db.py +18 -0
- slidge/core/mixins/disco.py +0 -10
- slidge/core/mixins/message.py +18 -8
- slidge/core/mixins/message_maker.py +17 -9
- slidge/core/mixins/presence.py +17 -4
- slidge/core/pubsub.py +54 -220
- slidge/core/session.py +69 -34
- slidge/db/__init__.py +4 -0
- slidge/db/alembic/env.py +64 -0
- slidge/db/alembic/script.py.mako +26 -0
- slidge/db/alembic/versions/09f27f098baa_add_missing_attributes_in_room.py +36 -0
- slidge/db/alembic/versions/2461390c0af2_store_contacts_caps_verstring_in_db.py +36 -0
- slidge/db/alembic/versions/29f5280c61aa_store_subject_setter_in_room.py +37 -0
- slidge/db/alembic/versions/2b1f45ab7379_store_room_subject_setter_by_nickname.py +41 -0
- slidge/db/alembic/versions/82a4af84b679_add_muc_history_filled.py +48 -0
- slidge/db/alembic/versions/8d2ced764698_rely_on_db_to_store_contacts_rooms_and_.py +133 -0
- slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +85 -0
- slidge/db/alembic/versions/b33993e87db3_move_everything_to_persistent_db.py +214 -0
- slidge/db/alembic/versions/b64b1a793483_add_source_and_legacy_id_for_archived_.py +48 -0
- slidge/db/alembic/versions/c4a8ec35a0e8_per_room_user_nick.py +34 -0
- slidge/db/alembic/versions/e91195719c2c_store_users_avatars_persistently.py +26 -0
- slidge/db/avatar.py +235 -0
- slidge/db/meta.py +65 -0
- slidge/db/models.py +375 -0
- slidge/db/store.py +1078 -0
- slidge/group/archive.py +58 -14
- slidge/group/bookmarks.py +72 -57
- slidge/group/participant.py +87 -28
- slidge/group/room.py +369 -211
- slidge/main.py +201 -0
- slidge/migration.py +30 -0
- slidge/slixfix/__init__.py +35 -2
- slidge/slixfix/roster.py +11 -4
- slidge/slixfix/xep_0292/vcard4.py +3 -0
- slidge/util/archive_msg.py +2 -1
- slidge/util/db.py +1 -47
- slidge/util/test.py +71 -4
- slidge/util/types.py +29 -4
- slidge/util/util.py +22 -0
- {slidge-0.1.3.dist-info → slidge-0.2.0a1.dist-info}/METADATA +4 -4
- slidge-0.2.0a1.dist-info/RECORD +114 -0
- slidge/core/cache.py +0 -183
- slidge/util/schema.sql +0 -126
- slidge/util/sql.py +0 -508
- slidge-0.1.3.dist-info/RECORD +0 -96
- {slidge-0.1.3.dist-info → slidge-0.2.0a1.dist-info}/LICENSE +0 -0
- {slidge-0.1.3.dist-info → slidge-0.2.0a1.dist-info}/WHEEL +0 -0
- {slidge-0.1.3.dist-info → slidge-0.2.0a1.dist-info}/entry_points.txt +0 -0
slidge/core/mixins/avatar.py
CHANGED
@@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Optional
|
|
5
5
|
|
6
6
|
from slixmpp import JID
|
7
7
|
|
8
|
+
from ...db.avatar import CachedAvatar, avatar_cache
|
8
9
|
from ...util.types import (
|
9
10
|
URL,
|
10
11
|
AnyBaseSession,
|
@@ -12,7 +13,6 @@ from ...util.types import (
|
|
12
13
|
AvatarType,
|
13
14
|
LegacyFileIdType,
|
14
15
|
)
|
15
|
-
from ..cache import avatar_cache
|
16
16
|
|
17
17
|
if TYPE_CHECKING:
|
18
18
|
from ..pubsub import PepAvatar
|
@@ -28,13 +28,14 @@ class AvatarMixin:
|
|
28
28
|
|
29
29
|
jid: JID = NotImplemented
|
30
30
|
session: AnyBaseSession = NotImplemented
|
31
|
-
_avatar_pubsub_broadcast: bool = NotImplemented
|
32
31
|
_avatar_bare_jid: bool = NotImplemented
|
33
32
|
|
34
33
|
def __init__(self) -> None:
|
35
34
|
super().__init__()
|
36
35
|
self._set_avatar_task: Optional[Task] = None
|
36
|
+
self.__broadcast_task: Optional[Task] = None
|
37
37
|
self.__avatar_unique_id: Optional[AvatarIdType] = None
|
38
|
+
self._avatar_pk: Optional[int] = None
|
38
39
|
|
39
40
|
@property
|
40
41
|
def __avatar_jid(self):
|
@@ -84,17 +85,42 @@ class AvatarMixin:
|
|
84
85
|
return None
|
85
86
|
raise TypeError("Bad avatar", a)
|
86
87
|
|
87
|
-
async def __set_avatar(
|
88
|
+
async def __set_avatar(
|
89
|
+
self, a: Optional[AvatarType], uid: Optional[AvatarIdType], delete: bool
|
90
|
+
):
|
88
91
|
self.__avatar_unique_id = uid
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
92
|
+
|
93
|
+
if a is None:
|
94
|
+
cached_avatar = None
|
95
|
+
self._avatar_pk = None
|
96
|
+
else:
|
97
|
+
try:
|
98
|
+
cached_avatar = await avatar_cache.convert_or_get(
|
99
|
+
URL(a) if isinstance(a, URL) else a,
|
100
|
+
None if isinstance(uid, URL) else uid,
|
101
|
+
)
|
102
|
+
except Exception as e:
|
103
|
+
self.session.log.error("Failed to set avatar %s", a, exc_info=e)
|
104
|
+
self._avatar_pk = None
|
105
|
+
self.__avatar_unique_id = uid
|
106
|
+
return
|
107
|
+
self._avatar_pk = cached_avatar.pk
|
108
|
+
|
109
|
+
if self.__should_pubsub_broadcast():
|
110
|
+
await self.session.xmpp.pubsub.broadcast_avatar(
|
111
|
+
self.__avatar_jid, self.session.user_jid, cached_avatar
|
112
|
+
)
|
113
|
+
|
114
|
+
if delete and isinstance(a, Path):
|
115
|
+
a.unlink()
|
116
|
+
|
96
117
|
self._post_avatar_update()
|
97
118
|
|
119
|
+
def __should_pubsub_broadcast(self):
|
120
|
+
return getattr(self, "is_friend", False) and getattr(
|
121
|
+
self, "added_to_roster", False
|
122
|
+
)
|
123
|
+
|
98
124
|
async def _no_change(self, a: Optional[AvatarType], uid: Optional[AvatarIdType]):
|
99
125
|
if a is None:
|
100
126
|
return self.__avatar_unique_id is None
|
@@ -103,23 +129,27 @@ class AvatarMixin:
|
|
103
129
|
if isinstance(uid, URL):
|
104
130
|
if self.__avatar_unique_id != uid:
|
105
131
|
return False
|
106
|
-
return not await avatar_cache.
|
132
|
+
return not await avatar_cache.url_modified(uid)
|
107
133
|
return self.__avatar_unique_id == uid
|
108
134
|
|
109
135
|
async def set_avatar(
|
110
136
|
self,
|
111
137
|
a: Optional[AvatarType],
|
112
138
|
avatar_unique_id: Optional[LegacyFileIdType] = None,
|
139
|
+
delete: bool = False,
|
113
140
|
blocking=False,
|
114
141
|
cancel=True,
|
115
142
|
) -> None:
|
116
143
|
"""
|
117
144
|
Set an avatar for this entity
|
118
145
|
|
119
|
-
:param a:
|
120
|
-
:param avatar_unique_id:
|
121
|
-
|
122
|
-
:param
|
146
|
+
:param a: The avatar, in one of the types slidge supports
|
147
|
+
:param avatar_unique_id: A globally unique ID for the avatar on the
|
148
|
+
legacy network
|
149
|
+
:param delete: If the avatar is provided as a Path, whether to delete
|
150
|
+
it once used or not.
|
151
|
+
:param blocking: Internal use by slidge for tests, do not use!
|
152
|
+
:param cancel: Internal use by slidge, do not use!
|
123
153
|
"""
|
124
154
|
if avatar_unique_id is None and a is not None:
|
125
155
|
avatar_unique_id = self.__get_uid(a)
|
@@ -128,7 +158,7 @@ class AvatarMixin:
|
|
128
158
|
if cancel and self._set_avatar_task:
|
129
159
|
self._set_avatar_task.cancel()
|
130
160
|
awaitable = create_task(
|
131
|
-
self.__set_avatar(a, avatar_unique_id),
|
161
|
+
self.__set_avatar(a, avatar_unique_id, delete),
|
132
162
|
name=f"Set pubsub avatar of {self}",
|
133
163
|
)
|
134
164
|
if not self._set_avatar_task or self._set_avatar_task.done():
|
@@ -136,32 +166,67 @@ class AvatarMixin:
|
|
136
166
|
if blocking:
|
137
167
|
await awaitable
|
138
168
|
|
139
|
-
def
|
169
|
+
def get_cached_avatar(self) -> Optional["CachedAvatar"]:
|
140
170
|
if not self.__avatar_unique_id:
|
141
171
|
return None
|
142
|
-
return
|
172
|
+
return avatar_cache.get(self.__avatar_unique_id)
|
173
|
+
|
174
|
+
def get_avatar(self) -> Optional["PepAvatar"]:
|
175
|
+
cached_avatar = self.get_cached_avatar()
|
176
|
+
if cached_avatar is None:
|
177
|
+
return None
|
178
|
+
from ..pubsub import PepAvatar
|
179
|
+
|
180
|
+
item = PepAvatar()
|
181
|
+
item.set_avatar_from_cache(cached_avatar)
|
182
|
+
return item
|
143
183
|
|
144
184
|
def _post_avatar_update(self) -> None:
|
145
185
|
return
|
146
186
|
|
187
|
+
def __get_cached_avatar_id(self):
|
188
|
+
i = self._get_cached_avatar_id()
|
189
|
+
if i is None:
|
190
|
+
return None
|
191
|
+
return self.session.xmpp.AVATAR_ID_TYPE(i)
|
192
|
+
|
193
|
+
def _get_cached_avatar_id(self) -> Optional[str]:
|
194
|
+
raise NotImplementedError
|
195
|
+
|
147
196
|
async def avatar_wrap_update_info(self):
|
148
|
-
cached_id =
|
197
|
+
cached_id = self.__get_cached_avatar_id()
|
149
198
|
self.__avatar_unique_id = cached_id
|
150
199
|
try:
|
151
200
|
await self.update_info() # type:ignore
|
152
201
|
except NotImplementedError:
|
153
202
|
return
|
154
203
|
new_id = self.avatar
|
155
|
-
if isinstance(new_id, URL) and not await avatar_cache.
|
204
|
+
if isinstance(new_id, URL) and not await avatar_cache.url_modified(new_id):
|
156
205
|
return
|
157
206
|
elif new_id != cached_id:
|
158
207
|
# at this point it means that update_info set the avatar, and we don't
|
159
208
|
# need to do anything else
|
160
209
|
return
|
161
210
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
self.
|
211
|
+
if self.__should_pubsub_broadcast():
|
212
|
+
if new_id is None and cached_id is None:
|
213
|
+
return
|
214
|
+
cached_avatar = avatar_cache.get(cached_id)
|
215
|
+
self.__broadcast_task = self.session.xmpp.loop.create_task(
|
216
|
+
self.session.xmpp.pubsub.broadcast_avatar(
|
217
|
+
self.__avatar_jid, self.session.user_jid, cached_avatar
|
218
|
+
)
|
219
|
+
)
|
220
|
+
|
221
|
+
def _set_avatar_from_store(self, stored):
|
222
|
+
if stored.avatar_id is None:
|
223
|
+
return
|
224
|
+
if stored.avatar is None:
|
225
|
+
# seems to happen after avatar cleanup for some reason?
|
226
|
+
self.__avatar_unique_id = None
|
227
|
+
return
|
228
|
+
self.__avatar_unique_id = (
|
229
|
+
stored.avatar.legacy_id
|
230
|
+
if stored.avatar.legacy_id is not None
|
231
|
+
else URL(stored.avatar.url)
|
167
232
|
)
|
slidge/core/mixins/base.py
CHANGED
@@ -8,7 +8,6 @@ from ...util.types import MessageOrPresenceTypeVar
|
|
8
8
|
if TYPE_CHECKING:
|
9
9
|
from slidge.core.gateway import BaseGateway
|
10
10
|
from slidge.core.session import BaseSession
|
11
|
-
from slidge.util.db import GatewayUser
|
12
11
|
|
13
12
|
|
14
13
|
class MetaBase(ABCMeta):
|
@@ -18,11 +17,18 @@ class MetaBase(ABCMeta):
|
|
18
17
|
class Base:
|
19
18
|
session: "BaseSession" = NotImplemented
|
20
19
|
xmpp: "BaseGateway" = NotImplemented
|
21
|
-
user: "GatewayUser" = NotImplemented
|
22
20
|
|
23
21
|
jid: JID = NotImplemented
|
24
22
|
name: str = NotImplemented
|
25
23
|
|
24
|
+
@property
|
25
|
+
def user_jid(self):
|
26
|
+
return self.session.user_jid
|
27
|
+
|
28
|
+
@property
|
29
|
+
def user_pk(self):
|
30
|
+
return self.session.user_pk
|
31
|
+
|
26
32
|
|
27
33
|
class BaseSender(Base):
|
28
34
|
def _send(
|
slidge/core/mixins/db.py
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
from contextlib import contextmanager
|
2
|
+
|
3
|
+
|
4
|
+
class UpdateInfoMixin:
|
5
|
+
"""
|
6
|
+
This mixin just adds a context manager that prevents commiting to the DB
|
7
|
+
on every attribute change.
|
8
|
+
"""
|
9
|
+
|
10
|
+
def __init__(self, *args, **kwargs):
|
11
|
+
super().__init__(*args, **kwargs)
|
12
|
+
self._updating_info = False
|
13
|
+
|
14
|
+
@contextmanager
|
15
|
+
def updating_info(self):
|
16
|
+
self._updating_info = True
|
17
|
+
yield
|
18
|
+
self._updating_info = False
|
slidge/core/mixins/disco.py
CHANGED
@@ -13,10 +13,6 @@ class BaseDiscoMixin(Base):
|
|
13
13
|
DISCO_NAME: str = NotImplemented
|
14
14
|
DISCO_LANG = None
|
15
15
|
|
16
|
-
def __init__(self):
|
17
|
-
super().__init__()
|
18
|
-
self.__caps_cache: Optional[str] = None
|
19
|
-
|
20
16
|
def _get_disco_name(self):
|
21
17
|
if self.DISCO_NAME is NotImplemented:
|
22
18
|
return self.xmpp.COMPONENT_NAME
|
@@ -44,17 +40,11 @@ class BaseDiscoMixin(Base):
|
|
44
40
|
return info
|
45
41
|
|
46
42
|
async def get_caps_ver(self, jid: OptJid = None, node: Optional[str] = None):
|
47
|
-
if self.__caps_cache:
|
48
|
-
return self.__caps_cache
|
49
43
|
info = await self.get_disco_info(jid, node)
|
50
44
|
caps = self.xmpp.plugin["xep_0115"]
|
51
45
|
ver = caps.generate_verstring(info, caps.hash)
|
52
|
-
self.__caps_cache = ver
|
53
46
|
return ver
|
54
47
|
|
55
|
-
def reset_caps_cache(self):
|
56
|
-
self.__caps_cache = None
|
57
|
-
|
58
48
|
|
59
49
|
class ChatterDiscoMixin(BaseDiscoMixin):
|
60
50
|
AVATAR = True
|
slidge/core/mixins/message.py
CHANGED
@@ -111,7 +111,7 @@ class MarkerMixin(MessageMaker):
|
|
111
111
|
self.xmpp.delivery_receipt.make_ack(
|
112
112
|
self._legacy_to_xmpp(legacy_msg_id),
|
113
113
|
mfrom=self.jid,
|
114
|
-
mto=self.
|
114
|
+
mto=self.user_jid,
|
115
115
|
)
|
116
116
|
)
|
117
117
|
self._send(
|
@@ -144,7 +144,7 @@ class MarkerMixin(MessageMaker):
|
|
144
144
|
# We'll see if we need to implement that later
|
145
145
|
return
|
146
146
|
xmpp_msg_id = self._legacy_to_xmpp(legacy_msg_id)
|
147
|
-
iq = Iq(sto=self.
|
147
|
+
iq = Iq(sto=self.user_jid.bare, sfrom=self.user_jid.bare, stype="set")
|
148
148
|
iq["pubsub"]["publish"]["node"] = self.xmpp["xep_0490"].stanza.NS
|
149
149
|
iq["pubsub"]["publish"]["item"]["id"] = muc_jid
|
150
150
|
displayed = self.xmpp["xep_0490"].stanza.Displayed()
|
@@ -169,8 +169,8 @@ class ContentMessageMixin(AttachmentMixin):
|
|
169
169
|
|
170
170
|
def __replace_id(self, legacy_msg_id: LegacyMessageType):
|
171
171
|
if self.mtype == "groupchat":
|
172
|
-
return self.
|
173
|
-
legacy_msg_id
|
172
|
+
return self.xmpp.store.sent.get_group_xmpp_id(
|
173
|
+
self.session.user_pk, str(legacy_msg_id)
|
174
174
|
) or self._legacy_to_xmpp(legacy_msg_id)
|
175
175
|
else:
|
176
176
|
return self._legacy_to_xmpp(legacy_msg_id)
|
@@ -215,14 +215,18 @@ class ContentMessageMixin(AttachmentMixin):
|
|
215
215
|
but store it in the archive. Meant to be used during ``MUC.backfill()``
|
216
216
|
"""
|
217
217
|
if carbon:
|
218
|
-
if not correction and
|
218
|
+
if not correction and self.xmpp.store.sent.was_sent_by_user(
|
219
|
+
self.session.user_pk, str(legacy_msg_id)
|
220
|
+
):
|
219
221
|
log.warning(
|
220
222
|
"Carbon message for a message an XMPP has sent? This is a bug! %s",
|
221
223
|
legacy_msg_id,
|
222
224
|
)
|
223
225
|
return
|
224
|
-
self.
|
225
|
-
|
226
|
+
self.xmpp.store.sent.set_message(
|
227
|
+
self.session.user_pk,
|
228
|
+
str(legacy_msg_id),
|
229
|
+
self.session.legacy_to_xmpp_msg_id(legacy_msg_id),
|
226
230
|
)
|
227
231
|
hints = self.__default_hints(hints)
|
228
232
|
msg = self._make_message(
|
@@ -237,7 +241,13 @@ class ContentMessageMixin(AttachmentMixin):
|
|
237
241
|
)
|
238
242
|
if correction:
|
239
243
|
msg["replace"]["id"] = self.__replace_id(legacy_msg_id)
|
240
|
-
return self._send(
|
244
|
+
return self._send(
|
245
|
+
msg,
|
246
|
+
archive_only=archive_only,
|
247
|
+
carbon=carbon,
|
248
|
+
legacy_msg_id=legacy_msg_id,
|
249
|
+
**send_kwargs,
|
250
|
+
)
|
241
251
|
|
242
252
|
def correct(
|
243
253
|
self,
|
@@ -7,8 +7,8 @@ from uuid import uuid4
|
|
7
7
|
from slixmpp import Message
|
8
8
|
from slixmpp.types import MessageTypes
|
9
9
|
|
10
|
+
from ...db.models import GatewayUser
|
10
11
|
from ...slixfix.link_preview.stanza import LinkPreview as LinkPreviewStanza
|
11
|
-
from ...util.db import GatewayUser
|
12
12
|
from ...util.types import (
|
13
13
|
ChatState,
|
14
14
|
LegacyMessageType,
|
@@ -60,8 +60,9 @@ class MessageMaker(BaseSender):
|
|
60
60
|
msg["body"] = body
|
61
61
|
state = "active"
|
62
62
|
if thread:
|
63
|
-
|
64
|
-
|
63
|
+
msg["thread"] = self.xmpp.store.sent.get_legacy_thread(
|
64
|
+
self.user_pk, str(thread)
|
65
|
+
) or str(thread)
|
65
66
|
if state:
|
66
67
|
msg["chat_state"] = state
|
67
68
|
for hint in hints:
|
@@ -88,9 +89,9 @@ class MessageMaker(BaseSender):
|
|
88
89
|
msg["stanza_id"]["by"] = self.muc.jid # type: ignore
|
89
90
|
|
90
91
|
def _legacy_to_xmpp(self, legacy_id: LegacyMessageType):
|
91
|
-
return self.
|
92
|
-
legacy_id
|
93
|
-
)
|
92
|
+
return self.xmpp.store.sent.get_xmpp_id(
|
93
|
+
self.session.user_pk, str(legacy_id)
|
94
|
+
) or self.session.legacy_to_xmpp_msg_id(legacy_id)
|
94
95
|
|
95
96
|
def _add_delay(self, msg: Message, when: Optional[datetime]):
|
96
97
|
if when:
|
@@ -110,16 +111,23 @@ class MessageMaker(BaseSender):
|
|
110
111
|
muc = getattr(self, "muc", None)
|
111
112
|
|
112
113
|
if entity := reply_to.author:
|
113
|
-
if isinstance(entity, GatewayUser):
|
114
|
+
if entity == "user" or isinstance(entity, GatewayUser):
|
115
|
+
if isinstance(entity, GatewayUser):
|
116
|
+
warnings.warn(
|
117
|
+
"Using a GatewayUser as the author of a "
|
118
|
+
"MessageReference is deprecated. Use the string 'user' "
|
119
|
+
"instead.",
|
120
|
+
DeprecationWarning,
|
121
|
+
)
|
114
122
|
if muc:
|
115
123
|
jid = copy(muc.jid)
|
116
124
|
jid.resource = fallback_nick = muc.user_nick
|
117
125
|
msg["reply"]["to"] = jid
|
118
126
|
else:
|
119
|
-
msg["reply"]["to"] =
|
127
|
+
msg["reply"]["to"] = self.session.user_jid
|
120
128
|
# TODO: here we should use preferably use the PEP nick of the user
|
121
129
|
# (but it doesn't matter much)
|
122
|
-
fallback_nick =
|
130
|
+
fallback_nick = self.session.user_jid.local
|
123
131
|
else:
|
124
132
|
if muc:
|
125
133
|
if hasattr(entity, "muc"):
|
slidge/core/mixins/presence.py
CHANGED
@@ -5,7 +5,7 @@ from typing import Optional
|
|
5
5
|
|
6
6
|
from slixmpp.types import PresenceShows, PresenceTypes
|
7
7
|
|
8
|
-
from ...util.
|
8
|
+
from ...util.types import CachedPresence
|
9
9
|
from .. import config
|
10
10
|
from .base import BaseSender
|
11
11
|
|
@@ -19,20 +19,32 @@ _FRIEND_REQUEST_PRESENCES = {"subscribe", "unsubscribe", "subscribed", "unsubscr
|
|
19
19
|
|
20
20
|
class PresenceMixin(BaseSender):
|
21
21
|
_ONLY_SEND_PRESENCE_CHANGES = False
|
22
|
+
contact_pk: Optional[int] = None
|
22
23
|
|
23
24
|
def __init__(self, *a, **k):
|
24
25
|
super().__init__(*a, **k)
|
26
|
+
# FIXME: this should not be an attribute of this mixin to allow garbage
|
27
|
+
# collection of instances
|
25
28
|
self.__update_last_seen_fallback_task: Optional[Task] = None
|
29
|
+
# this is only used when a presence is set during Contact.update_info(),
|
30
|
+
# when the contact does not have a DB primary key yet, and is written
|
31
|
+
# to DB at the end of update_info()
|
32
|
+
self.cached_presence: Optional[CachedPresence] = None
|
26
33
|
|
27
34
|
async def __update_last_seen_fallback(self):
|
28
35
|
await sleep(3600 * 7)
|
29
36
|
self.send_last_presence(force=True, no_cache_online=False)
|
30
37
|
|
31
38
|
def _get_last_presence(self) -> Optional[CachedPresence]:
|
32
|
-
|
39
|
+
if self.contact_pk is None:
|
40
|
+
return None
|
41
|
+
return self.xmpp.store.contacts.get_presence(self.contact_pk)
|
33
42
|
|
34
43
|
def _store_last_presence(self, new: CachedPresence):
|
35
|
-
|
44
|
+
if self.contact_pk is None:
|
45
|
+
self.cached_presence = new
|
46
|
+
return
|
47
|
+
self.xmpp.store.contacts.set_presence(self.contact_pk, new)
|
36
48
|
|
37
49
|
def _make_presence(
|
38
50
|
self,
|
@@ -55,7 +67,8 @@ class PresenceMixin(BaseSender):
|
|
55
67
|
)
|
56
68
|
if old != new:
|
57
69
|
if hasattr(self, "muc") and ptype == "unavailable":
|
58
|
-
|
70
|
+
if self.contact_pk is not None:
|
71
|
+
self.xmpp.store.contacts.reset_presence(self.contact_pk)
|
59
72
|
else:
|
60
73
|
self._store_last_presence(new)
|
61
74
|
if old and not force and self._ONLY_SEND_PRESENCE_CHANGES:
|