slidge 0.2.0a6__py3-none-any.whl → 0.2.0a9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- slidge/__version__.py +1 -1
- slidge/command/chat_command.py +15 -1
- slidge/command/user.py +2 -0
- slidge/contact/contact.py +46 -13
- slidge/contact/roster.py +0 -3
- slidge/core/gateway/base.py +15 -7
- slidge/core/gateway/session_dispatcher.py +16 -7
- slidge/core/mixins/attachment.py +51 -46
- slidge/core/mixins/avatar.py +11 -7
- slidge/core/pubsub.py +55 -68
- slidge/core/session.py +4 -4
- slidge/db/alembic/versions/3071e0fa69d4_add_contact_client_type.py +52 -0
- slidge/db/alembic/versions/5bd48bfdffa2_lift_room_legacy_id_constraint.py +19 -13
- slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +5 -1
- slidge/db/alembic/versions/abba1ae0edb3_store_avatar_legacy_id_in_the_contact_.py +78 -0
- slidge/db/avatar.py +14 -49
- slidge/db/models.py +8 -5
- slidge/db/store.py +30 -16
- slidge/group/room.py +17 -4
- slidge/main.py +8 -3
- slidge/migration.py +15 -1
- slidge/util/test.py +8 -1
- slidge/util/types.py +4 -0
- {slidge-0.2.0a6.dist-info → slidge-0.2.0a9.dist-info}/METADATA +6 -2
- {slidge-0.2.0a6.dist-info → slidge-0.2.0a9.dist-info}/RECORD +28 -26
- {slidge-0.2.0a6.dist-info → slidge-0.2.0a9.dist-info}/LICENSE +0 -0
- {slidge-0.2.0a6.dist-info → slidge-0.2.0a9.dist-info}/WHEEL +0 -0
- {slidge-0.2.0a6.dist-info → slidge-0.2.0a9.dist-info}/entry_points.txt +0 -0
slidge/__version__.py
CHANGED
slidge/command/chat_command.py
CHANGED
@@ -153,6 +153,9 @@ class ChatCommandProvider:
|
|
153
153
|
self.xmpp.delivery_receipt.ack(msg)
|
154
154
|
return await self._handle_result(result, msg, session)
|
155
155
|
|
156
|
+
def __make_uri(self, body: str) -> str:
|
157
|
+
return f"xmpp:{self.xmpp.boundjid.bare}?message;body={body}"
|
158
|
+
|
156
159
|
async def _handle_result(self, result: CommandResponseType, msg: Message, session):
|
157
160
|
if isinstance(result, str) or result is None:
|
158
161
|
reply = msg.reply()
|
@@ -175,9 +178,14 @@ class ChatCommandProvider:
|
|
175
178
|
).send()
|
176
179
|
if f.options:
|
177
180
|
for o in f.options:
|
178
|
-
msg.reply(
|
181
|
+
msg.reply(
|
182
|
+
f"{o['label']}: {self.__make_uri(o['value'])}"
|
183
|
+
).send()
|
179
184
|
if f.value:
|
180
185
|
msg.reply(f"Default: {f.value}").send()
|
186
|
+
if f.type == "boolean":
|
187
|
+
msg.reply("yes: " + self.__make_uri("yes")).send()
|
188
|
+
msg.reply("no: " + self.__make_uri("no")).send()
|
181
189
|
|
182
190
|
ans = await self.xmpp.input(
|
183
191
|
msg.get_from(), (f.label or f.var) + "? (or 'abort')"
|
@@ -186,6 +194,12 @@ class ChatCommandProvider:
|
|
186
194
|
return await self._handle_result(
|
187
195
|
"Command aborted", msg, session
|
188
196
|
)
|
197
|
+
if f.type == "boolean":
|
198
|
+
if ans.lower() == "yes":
|
199
|
+
ans = "true"
|
200
|
+
else:
|
201
|
+
ans = "false"
|
202
|
+
|
189
203
|
if f.type.endswith("multi"):
|
190
204
|
form_values[f.var] = f.validate(ans.split(" "))
|
191
205
|
else:
|
slidge/command/user.py
CHANGED
@@ -260,6 +260,8 @@ class Preferences(Command):
|
|
260
260
|
self.xmpp.store.users.update(user)
|
261
261
|
if form_values["sync_avatar"]:
|
262
262
|
await self.xmpp.fetch_user_avatar(session)
|
263
|
+
else:
|
264
|
+
session.xmpp.store.users.set_avatar_hash(session.user_pk, None)
|
263
265
|
return "Your preferences have been updated."
|
264
266
|
|
265
267
|
|
slidge/contact/contact.py
CHANGED
@@ -17,7 +17,7 @@ from ..core.mixins.disco import ContactAccountDiscoMixin
|
|
17
17
|
from ..core.mixins.recipient import ReactionRecipientMixin, ThreadRecipientMixin
|
18
18
|
from ..db.models import Contact
|
19
19
|
from ..util import SubclassableOnce
|
20
|
-
from ..util.types import LegacyUserIdType, MessageOrPresenceTypeVar
|
20
|
+
from ..util.types import ClientType, LegacyUserIdType, MessageOrPresenceTypeVar
|
21
21
|
|
22
22
|
if TYPE_CHECKING:
|
23
23
|
from ..core.session import BaseSession
|
@@ -128,6 +128,7 @@ class LegacyContact(
|
|
128
128
|
self._caps_ver: str | None = None
|
129
129
|
self._vcard_fetched = False
|
130
130
|
self._vcard: str | None = None
|
131
|
+
self._client_type: ClientType = "pc"
|
131
132
|
|
132
133
|
async def get_vcard(self, fetch=True) -> VCard4 | None:
|
133
134
|
if fetch and not self._vcard_fetched:
|
@@ -187,6 +188,32 @@ class LegacyContact(
|
|
187
188
|
def user_jid(self):
|
188
189
|
return self.session.user_jid
|
189
190
|
|
191
|
+
@property # type:ignore
|
192
|
+
def DISCO_TYPE(self) -> ClientType:
|
193
|
+
return self._client_type
|
194
|
+
|
195
|
+
@DISCO_TYPE.setter
|
196
|
+
def DISCO_TYPE(self, value: ClientType) -> None:
|
197
|
+
self.client_type = value
|
198
|
+
|
199
|
+
@property
|
200
|
+
def client_type(self) -> ClientType:
|
201
|
+
"""
|
202
|
+
The client type of this contact, cf https://xmpp.org/registrar/disco-categories.html#client
|
203
|
+
|
204
|
+
Default is "pc".
|
205
|
+
"""
|
206
|
+
return self._client_type
|
207
|
+
|
208
|
+
@client_type.setter
|
209
|
+
def client_type(self, value: ClientType) -> None:
|
210
|
+
self._client_type = value
|
211
|
+
if self._updating_info:
|
212
|
+
return
|
213
|
+
self.__ensure_pk()
|
214
|
+
assert self.contact_pk is not None
|
215
|
+
self.xmpp.store.contacts.set_client_type(self.contact_pk, value)
|
216
|
+
|
190
217
|
def _set_logger_name(self):
|
191
218
|
self.log.name = f"{self.user_jid.bare}:contact:{self}"
|
192
219
|
|
@@ -255,8 +282,9 @@ class LegacyContact(
|
|
255
282
|
self._privileged_send(stanza)
|
256
283
|
return stanza # type:ignore
|
257
284
|
|
258
|
-
if
|
259
|
-
self.
|
285
|
+
if isinstance(stanza, Presence):
|
286
|
+
if not self._updating_info:
|
287
|
+
self.__propagate_to_participants(stanza)
|
260
288
|
if (
|
261
289
|
not self.is_friend
|
262
290
|
and stanza["type"] not in self._NON_FRIEND_PRESENCES_FILTER
|
@@ -329,14 +357,13 @@ class LegacyContact(
|
|
329
357
|
return self.xmpp.store.contacts.get_avatar_legacy_id(self.contact_pk)
|
330
358
|
|
331
359
|
def _post_avatar_update(self):
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
self.
|
338
|
-
|
339
|
-
self.xmpp.store.contacts.set_avatar(self.contact_pk, self._avatar_pk)
|
360
|
+
self.__ensure_pk()
|
361
|
+
assert self.contact_pk is not None
|
362
|
+
self.xmpp.store.contacts.set_avatar(
|
363
|
+
self.contact_pk,
|
364
|
+
self._avatar_pk,
|
365
|
+
None if self.avatar_id is None else str(self.avatar_id),
|
366
|
+
)
|
340
367
|
for p in self.participants:
|
341
368
|
self.log.debug("Propagating new avatar to %s", p.muc)
|
342
369
|
p.send_last_presence(force=True, no_cache_online=True)
|
@@ -603,14 +630,20 @@ class LegacyContact(
|
|
603
630
|
contact.contact_pk = stored.id
|
604
631
|
contact._name = stored.nick
|
605
632
|
contact._is_friend = stored.is_friend
|
606
|
-
contact.
|
633
|
+
contact._added_to_roster = stored.added_to_roster
|
607
634
|
if (data := stored.extra_attributes) is not None:
|
608
635
|
contact.deserialize_extra_attributes(data)
|
609
636
|
contact._caps_ver = stored.caps_ver
|
610
637
|
contact._set_logger_name()
|
611
|
-
contact.
|
638
|
+
contact._AvatarMixin__avatar_unique_id = ( # type:ignore
|
639
|
+
None
|
640
|
+
if stored.avatar_legacy_id is None
|
641
|
+
else session.xmpp.AVATAR_ID_TYPE(stored.avatar_legacy_id)
|
642
|
+
)
|
643
|
+
contact._avatar_pk = stored.avatar_id
|
612
644
|
contact._vcard = stored.vcard
|
613
645
|
contact._vcard_fetched = stored.vcard_fetched
|
646
|
+
contact._client_type = stored.client_type
|
614
647
|
return contact
|
615
648
|
|
616
649
|
|
slidge/contact/roster.py
CHANGED
@@ -150,10 +150,7 @@ class LegacyRoster(
|
|
150
150
|
except Exception as e:
|
151
151
|
raise XMPPError("internal-server-error", str(e))
|
152
152
|
contact._caps_ver = await contact.get_caps_ver(contact.jid)
|
153
|
-
need_avatar = contact.contact_pk is None
|
154
153
|
contact.contact_pk = self.__store.update(contact, commit=not self.__filling)
|
155
|
-
if need_avatar:
|
156
|
-
contact._post_avatar_update()
|
157
154
|
return contact
|
158
155
|
|
159
156
|
async def by_stanza(self, s) -> LegacyContact:
|
slidge/core/gateway/base.py
CHANGED
@@ -270,6 +270,8 @@ class BaseGateway(
|
|
270
270
|
Common example: ``int``.
|
271
271
|
"""
|
272
272
|
|
273
|
+
http: aiohttp.ClientSession
|
274
|
+
|
273
275
|
def __init__(self):
|
274
276
|
self.log = log
|
275
277
|
self.datetime_started = datetime.now()
|
@@ -303,7 +305,7 @@ class BaseGateway(
|
|
303
305
|
fix_error_ns=True,
|
304
306
|
)
|
305
307
|
self.loop.set_exception_handler(self.__exception_handler)
|
306
|
-
self.
|
308
|
+
self.loop.create_task(self.__set_http())
|
307
309
|
self.has_crashed: bool = False
|
308
310
|
self.use_origin_id = False
|
309
311
|
|
@@ -370,6 +372,12 @@ class BaseGateway(
|
|
370
372
|
|
371
373
|
MessageMixin.__init__(self) # ComponentXMPP does not call super().__init__()
|
372
374
|
|
375
|
+
async def __set_http(self):
|
376
|
+
self.http = aiohttp.ClientSession()
|
377
|
+
if getattr(self, "_test_mode", False):
|
378
|
+
return
|
379
|
+
avatar_cache.http = self.http
|
380
|
+
|
373
381
|
async def __mam_cleanup(self):
|
374
382
|
if not config.MAM_MAX_DAYS:
|
375
383
|
return
|
@@ -455,10 +463,8 @@ class BaseGateway(
|
|
455
463
|
await disco.del_feature(feature="urn:xmpp:http:upload:0", jid=self.boundjid)
|
456
464
|
await self.plugin["xep_0115"].update_caps(jid=self.boundjid)
|
457
465
|
|
458
|
-
if self.COMPONENT_AVATAR:
|
459
|
-
cached_avatar = await avatar_cache.convert_or_get(
|
460
|
-
self.COMPONENT_AVATAR, None
|
461
|
-
)
|
466
|
+
if self.COMPONENT_AVATAR is not None:
|
467
|
+
cached_avatar = await avatar_cache.convert_or_get(self.COMPONENT_AVATAR)
|
462
468
|
self.avatar_pk = cached_avatar.pk
|
463
469
|
else:
|
464
470
|
cached_avatar = None
|
@@ -580,6 +586,8 @@ class BaseGateway(
|
|
580
586
|
session.send_gateway_status(status, show="chat")
|
581
587
|
if session.user.preferences.get("sync_avatar", False):
|
582
588
|
session.create_task(self.fetch_user_avatar(session))
|
589
|
+
else:
|
590
|
+
self.xmpp.store.users.set_avatar_hash(session.user_pk, None)
|
583
591
|
|
584
592
|
async def fetch_user_avatar(self, session: BaseSession):
|
585
593
|
try:
|
@@ -588,8 +596,8 @@ class BaseGateway(
|
|
588
596
|
self.xmpp.plugin["xep_0084"].stanza.MetaData.namespace,
|
589
597
|
ifrom=self.boundjid.bare,
|
590
598
|
)
|
591
|
-
except IqError
|
592
|
-
|
599
|
+
except IqError:
|
600
|
+
self.xmpp.store.users.set_avatar_hash(session.user_pk, None)
|
593
601
|
return
|
594
602
|
await self.__dispatcher.on_avatar_metadata_info(
|
595
603
|
session, iq["pubsub"]["items"]["item"]["avatar_metadata"]["info"]
|
@@ -31,7 +31,6 @@ class Ignore(BaseException):
|
|
31
31
|
class SessionDispatcher:
|
32
32
|
def __init__(self, xmpp: "BaseGateway"):
|
33
33
|
self.xmpp = xmpp
|
34
|
-
self.http = xmpp.http
|
35
34
|
|
36
35
|
xmpp.register_handler(
|
37
36
|
CoroutineCallback(
|
@@ -98,6 +97,10 @@ class SessionDispatcher:
|
|
98
97
|
event, _exceptions_to_xmpp_errors(getattr(self, "on_" + event))
|
99
98
|
)
|
100
99
|
|
100
|
+
@property
|
101
|
+
def http(self):
|
102
|
+
return self.xmpp.http
|
103
|
+
|
101
104
|
async def __get_session(
|
102
105
|
self, stanza: Union[Message, Presence, Iq], timeout: Optional[int] = 10
|
103
106
|
) -> BaseSession:
|
@@ -503,8 +506,19 @@ class SessionDispatcher:
|
|
503
506
|
resources,
|
504
507
|
merge_resources(resources),
|
505
508
|
)
|
509
|
+
if p.get_type() == "available":
|
510
|
+
await self.xmpp.pubsub.on_presence_available(p, None)
|
506
511
|
return
|
507
512
|
|
513
|
+
if p.get_type() == "available":
|
514
|
+
try:
|
515
|
+
contact = await session.contacts.by_jid(pto)
|
516
|
+
except XMPPError:
|
517
|
+
contact = None
|
518
|
+
if contact is not None:
|
519
|
+
await self.xmpp.pubsub.on_presence_available(p, contact)
|
520
|
+
return
|
521
|
+
|
508
522
|
muc = session.bookmarks.by_jid_only_if_exists(JID(pto.bare))
|
509
523
|
|
510
524
|
if muc is not None and p.get_type() == "unavailable":
|
@@ -580,12 +594,7 @@ class SessionDispatcher:
|
|
580
594
|
if session.user.avatar_hash == hash_:
|
581
595
|
session.log.debug("We already know this avatar hash")
|
582
596
|
return
|
583
|
-
|
584
|
-
user = self.xmpp.store.users.get(session.user_jid)
|
585
|
-
assert user is not None
|
586
|
-
user.avatar_hash = hash_
|
587
|
-
orm_session.add(user)
|
588
|
-
orm_session.commit()
|
597
|
+
self.xmpp.store.users.set_avatar_hash(session.user_pk, None)
|
589
598
|
|
590
599
|
if hash_:
|
591
600
|
try:
|
slidge/core/mixins/attachment.py
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import base64
|
1
2
|
import functools
|
2
3
|
import logging
|
3
4
|
import os
|
@@ -7,22 +8,23 @@ import stat
|
|
7
8
|
import tempfile
|
8
9
|
import warnings
|
9
10
|
from datetime import datetime
|
10
|
-
from
|
11
|
+
from itertools import chain
|
12
|
+
from mimetypes import guess_extension, guess_type
|
11
13
|
from pathlib import Path
|
12
14
|
from typing import IO, AsyncIterator, Collection, Optional, Sequence, Union
|
13
15
|
from urllib.parse import quote as urlquote
|
14
16
|
from uuid import uuid4
|
15
17
|
from xml.etree import ElementTree as ET
|
16
18
|
|
17
|
-
import
|
18
|
-
from PIL import Image
|
19
|
+
import thumbhash
|
20
|
+
from PIL import Image, ImageOps
|
19
21
|
from slixmpp import JID, Message
|
20
22
|
from slixmpp.exceptions import IqError
|
21
23
|
from slixmpp.plugins.xep_0363 import FileUploadError
|
22
|
-
from slixmpp.plugins.xep_0385.stanza import Sims
|
23
24
|
from slixmpp.plugins.xep_0447.stanza import StatelessFileSharing
|
24
25
|
|
25
26
|
from ...db.avatar import avatar_cache
|
27
|
+
from ...slixfix.xep_0264.stanza import Thumbnail
|
26
28
|
from ...util.types import (
|
27
29
|
LegacyAttachment,
|
28
30
|
LegacyMessageType,
|
@@ -169,7 +171,12 @@ class AttachmentMixin(MessageMaker):
|
|
169
171
|
)
|
170
172
|
|
171
173
|
if file_path is None:
|
172
|
-
|
174
|
+
if file_name is None:
|
175
|
+
file_name = str(uuid4())
|
176
|
+
if content_type is not None:
|
177
|
+
ext = guess_extension(content_type, strict=False) # type:ignore
|
178
|
+
if ext is not None:
|
179
|
+
file_name += ext
|
173
180
|
temp_dir = Path(tempfile.mkdtemp())
|
174
181
|
file_path = temp_dir / file_name
|
175
182
|
if file_url:
|
@@ -223,37 +230,44 @@ class AttachmentMixin(MessageMaker):
|
|
223
230
|
content_type: Optional[str] = None,
|
224
231
|
caption: Optional[str] = None,
|
225
232
|
file_name: Optional[str] = None,
|
226
|
-
):
|
233
|
+
) -> Thumbnail | None:
|
227
234
|
cache = self.__store.get_sims(uploaded_url)
|
228
235
|
if cache:
|
229
|
-
|
230
|
-
|
236
|
+
ref = self.xmpp["xep_0372"].stanza.Reference(xml=ET.fromstring(cache))
|
237
|
+
msg.append(ref)
|
238
|
+
if ref["sims"]["file"].get_plugin("thumbnail", check=True):
|
239
|
+
return ref["sims"]["file"]["thumbnail"]
|
240
|
+
else:
|
241
|
+
return None
|
231
242
|
|
232
243
|
if not path:
|
233
|
-
return
|
244
|
+
return None
|
234
245
|
|
235
|
-
|
246
|
+
ref = self.xmpp["xep_0385"].get_sims(
|
236
247
|
path, [uploaded_url], content_type, caption
|
237
248
|
)
|
238
249
|
if file_name:
|
239
|
-
|
250
|
+
ref["sims"]["file"]["name"] = file_name
|
251
|
+
thumbnail = None
|
240
252
|
if content_type is not None and content_type.startswith("image"):
|
241
253
|
try:
|
242
254
|
h, x, y = await self.xmpp.loop.run_in_executor(
|
243
|
-
avatar_cache._thread_pool,
|
255
|
+
avatar_cache._thread_pool, get_thumbhash, path
|
244
256
|
)
|
245
257
|
except Exception as e:
|
246
|
-
log.debug("Could not generate a
|
258
|
+
log.debug("Could not generate a thumbhash", exc_info=e)
|
247
259
|
else:
|
248
|
-
thumbnail =
|
260
|
+
thumbnail = ref["sims"]["file"]["thumbnail"]
|
249
261
|
thumbnail["width"] = x
|
250
262
|
thumbnail["height"] = y
|
251
|
-
thumbnail["media-type"] = "image/
|
252
|
-
thumbnail["uri"] = "data:image/
|
263
|
+
thumbnail["media-type"] = "image/thumbhash"
|
264
|
+
thumbnail["uri"] = "data:image/thumbhash," + urlquote(h)
|
265
|
+
|
266
|
+
self.__store.set_sims(uploaded_url, str(ref))
|
253
267
|
|
254
|
-
|
268
|
+
msg.append(ref)
|
255
269
|
|
256
|
-
|
270
|
+
return thumbnail
|
257
271
|
|
258
272
|
def __set_sfs(
|
259
273
|
self,
|
@@ -263,6 +277,7 @@ class AttachmentMixin(MessageMaker):
|
|
263
277
|
content_type: Optional[str] = None,
|
264
278
|
caption: Optional[str] = None,
|
265
279
|
file_name: Optional[str] = None,
|
280
|
+
thumbnail: Optional[Thumbnail] = None,
|
266
281
|
):
|
267
282
|
cache = self.__store.get_sfs(uploaded_url)
|
268
283
|
if cache:
|
@@ -275,6 +290,8 @@ class AttachmentMixin(MessageMaker):
|
|
275
290
|
sfs = self.xmpp["xep_0447"].get_sfs(path, [uploaded_url], content_type, caption)
|
276
291
|
if file_name:
|
277
292
|
sfs["file"]["name"] = file_name
|
293
|
+
if thumbnail is not None:
|
294
|
+
sfs["file"].append(thumbnail)
|
278
295
|
self.__store.set_sfs(uploaded_url, str(sfs))
|
279
296
|
|
280
297
|
msg.append(sfs)
|
@@ -371,10 +388,12 @@ class AttachmentMixin(MessageMaker):
|
|
371
388
|
self._set_msg_id(msg, legacy_msg_id)
|
372
389
|
return None, [self._send(msg, **kwargs)]
|
373
390
|
|
374
|
-
await self.__set_sims(
|
391
|
+
thumbnail = await self.__set_sims(
|
375
392
|
msg, new_url, local_path, content_type, caption, file_name
|
376
393
|
)
|
377
|
-
self.__set_sfs(
|
394
|
+
self.__set_sfs(
|
395
|
+
msg, new_url, local_path, content_type, caption, file_name, thumbnail
|
396
|
+
)
|
378
397
|
if is_temp and isinstance(local_path, Path):
|
379
398
|
local_path.unlink()
|
380
399
|
local_path.parent.rmdir()
|
@@ -493,32 +512,18 @@ class AttachmentMixin(MessageMaker):
|
|
493
512
|
)
|
494
513
|
|
495
514
|
|
496
|
-
def
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
# There are 2 blurhash-python packages:
|
509
|
-
# https://github.com/woltapp/blurhash-python
|
510
|
-
# https://github.com/halcy/blurhash-python
|
511
|
-
# With this hack we're compatible with both, which is useful for packaging
|
512
|
-
# without using pyproject.toml, as most distro do
|
513
|
-
try:
|
514
|
-
hash_ = blurhash.encode(img, x, y)
|
515
|
-
except TypeError:
|
516
|
-
# We are using halcy's blurhash which expects
|
517
|
-
# the 1st argument to be a 3-dimensional array
|
518
|
-
import numpy # type:ignore
|
519
|
-
|
520
|
-
hash_ = blurhash.encode(numpy.array(img.convert("RGB")), x, y)
|
521
|
-
return hash_, width, height
|
515
|
+
def get_thumbhash(path: Path) -> tuple[str, int, int]:
|
516
|
+
with path.open("rb") as fp:
|
517
|
+
img = Image.open(fp)
|
518
|
+
width, height = img.size
|
519
|
+
img = img.convert("RGBA")
|
520
|
+
if width > 100 or height > 100:
|
521
|
+
img.thumbnail((100, 100))
|
522
|
+
img = ImageOps.exif_transpose(img)
|
523
|
+
rgba_2d = list(img.getdata())
|
524
|
+
rgba = list(chain(*rgba_2d))
|
525
|
+
ints = thumbhash.rgba_to_thumb_hash(img.width, img.height, rgba)
|
526
|
+
return base64.b64encode(bytes(ints)).decode(), width, height
|
522
527
|
|
523
528
|
|
524
529
|
log = logging.getLogger(__name__)
|
slidge/core/mixins/avatar.py
CHANGED
@@ -73,6 +73,10 @@ class AvatarMixin:
|
|
73
73
|
name=f"Set avatar of {self} from property",
|
74
74
|
)
|
75
75
|
|
76
|
+
@property
|
77
|
+
def avatar_pk(self) -> int | None:
|
78
|
+
return self._avatar_pk
|
79
|
+
|
76
80
|
@staticmethod
|
77
81
|
def __get_uid(a: Optional[AvatarType]) -> Optional[AvatarIdType]:
|
78
82
|
if isinstance(a, str):
|
@@ -95,10 +99,7 @@ class AvatarMixin:
|
|
95
99
|
self._avatar_pk = None
|
96
100
|
else:
|
97
101
|
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
|
+
cached_avatar = await avatar_cache.convert_or_get(a)
|
102
103
|
except Exception as e:
|
103
104
|
self.session.log.error("Failed to set avatar %s", a, exc_info=e)
|
104
105
|
self._avatar_pk = None
|
@@ -167,9 +168,9 @@ class AvatarMixin:
|
|
167
168
|
await awaitable
|
168
169
|
|
169
170
|
def get_cached_avatar(self) -> Optional["CachedAvatar"]:
|
170
|
-
if
|
171
|
+
if self._avatar_pk is None:
|
171
172
|
return None
|
172
|
-
return avatar_cache.
|
173
|
+
return avatar_cache.get_by_pk(self._avatar_pk)
|
173
174
|
|
174
175
|
def get_avatar(self) -> Optional["PepAvatar"]:
|
175
176
|
cached_avatar = self.get_cached_avatar()
|
@@ -211,7 +212,10 @@ class AvatarMixin:
|
|
211
212
|
if self.__should_pubsub_broadcast():
|
212
213
|
if new_id is None and cached_id is None:
|
213
214
|
return
|
214
|
-
|
215
|
+
if self._avatar_pk is not None:
|
216
|
+
cached_avatar = avatar_cache.get_by_pk(self._avatar_pk)
|
217
|
+
else:
|
218
|
+
cached_avatar = None
|
215
219
|
self.__broadcast_task = self.session.xmpp.loop.create_task(
|
216
220
|
self.session.xmpp.pubsub.broadcast_avatar(
|
217
221
|
self.__avatar_jid, self.session.user_jid, cached_avatar
|