slidge 0.2.0a6__py3-none-any.whl → 0.2.0a9__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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
|