slidge 0.1.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 +61 -0
- slidge/__main__.py +192 -0
- 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 +3 -0
- slidge/core/cache.py +183 -0
- slidge/core/config.py +209 -0
- slidge/core/gateway/__init__.py +3 -0
- slidge/core/gateway/base.py +892 -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 +757 -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 +525 -0
- slidge/core/session.py +752 -0
- slidge/group/__init__.py +10 -0
- slidge/group/archive.py +125 -0
- slidge/group/bookmarks.py +163 -0
- slidge/group/participant.py +440 -0
- slidge/group/room.py +1095 -0
- slidge/migration.py +18 -0
- slidge/py.typed +0 -0
- slidge/slixfix/__init__.py +68 -0
- slidge/slixfix/link_preview/__init__.py +10 -0
- slidge/slixfix/link_preview/link_preview.py +17 -0
- slidge/slixfix/link_preview/stanza.py +99 -0
- slidge/slixfix/roster.py +60 -0
- slidge/slixfix/xep_0077/__init__.py +10 -0
- slidge/slixfix/xep_0077/register.py +289 -0
- slidge/slixfix/xep_0077/stanza.py +104 -0
- slidge/slixfix/xep_0100/__init__.py +5 -0
- slidge/slixfix/xep_0100/gateway.py +121 -0
- 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/slixfix/xep_0356_old/__init__.py +7 -0
- slidge/slixfix/xep_0356_old/privilege.py +167 -0
- slidge/slixfix/xep_0356_old/stanza.py +44 -0
- 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 +15 -0
- slidge/util/archive_msg.py +61 -0
- slidge/util/conf.py +206 -0
- slidge/util/db.py +229 -0
- slidge/util/schema.sql +126 -0
- slidge/util/sql.py +508 -0
- slidge/util/test.py +295 -0
- slidge/util/types.py +180 -0
- slidge/util/util.py +295 -0
- slidge-0.1.0.dist-info/LICENSE +661 -0
- slidge-0.1.0.dist-info/METADATA +109 -0
- slidge-0.1.0.dist-info/RECORD +96 -0
- slidge-0.1.0.dist-info/WHEEL +4 -0
- slidge-0.1.0.dist-info/entry_points.txt +3 -0
slidge/core/pubsub.py
ADDED
@@ -0,0 +1,525 @@
|
|
1
|
+
import hashlib
|
2
|
+
import io
|
3
|
+
import logging
|
4
|
+
from copy import copy
|
5
|
+
from pathlib import Path
|
6
|
+
from typing import TYPE_CHECKING, Optional, Type, Union
|
7
|
+
|
8
|
+
from PIL import Image, UnidentifiedImageError
|
9
|
+
from PIL.Image import Image as PILImage
|
10
|
+
from slixmpp import (
|
11
|
+
JID,
|
12
|
+
CoroutineCallback,
|
13
|
+
Iq,
|
14
|
+
Presence,
|
15
|
+
StanzaPath,
|
16
|
+
register_stanza_plugin,
|
17
|
+
)
|
18
|
+
from slixmpp.exceptions import XMPPError
|
19
|
+
from slixmpp.plugins.base import BasePlugin, register_plugin
|
20
|
+
from slixmpp.plugins.xep_0060.stanza import Event, EventItem, EventItems, Item
|
21
|
+
from slixmpp.plugins.xep_0084 import Data as AvatarData
|
22
|
+
from slixmpp.plugins.xep_0084 import MetaData as AvatarMetadata
|
23
|
+
from slixmpp.plugins.xep_0172 import UserNick
|
24
|
+
from slixmpp.plugins.xep_0292.stanza import VCard4
|
25
|
+
from slixmpp.types import JidStr, OptJidStr
|
26
|
+
|
27
|
+
from ..contact.contact import LegacyContact
|
28
|
+
from ..contact.roster import ContactIsUser
|
29
|
+
from ..util.db import GatewayUser, user_store
|
30
|
+
from ..util.sql import db
|
31
|
+
from ..util.types import AvatarType, LegacyFileIdType, PepItemType
|
32
|
+
from .cache import CachedAvatar, avatar_cache
|
33
|
+
from .mixins.lock import NamedLockMixin
|
34
|
+
|
35
|
+
if TYPE_CHECKING:
|
36
|
+
from slidge import BaseGateway
|
37
|
+
|
38
|
+
VCARD4_NAMESPACE = "urn:xmpp:vcard4"
|
39
|
+
|
40
|
+
|
41
|
+
class PepItem:
|
42
|
+
@staticmethod
|
43
|
+
def from_db(jid: JID, user: Optional[GatewayUser] = None) -> Optional["PepItem"]:
|
44
|
+
raise NotImplementedError
|
45
|
+
|
46
|
+
def to_db(self, jid: JID, user: Optional[GatewayUser] = None):
|
47
|
+
raise NotImplementedError
|
48
|
+
|
49
|
+
|
50
|
+
class PepAvatar(PepItem):
|
51
|
+
def __init__(self, jid: JID):
|
52
|
+
self.metadata: Optional[AvatarMetadata] = None
|
53
|
+
self.id: Optional[str] = None
|
54
|
+
self.jid = jid
|
55
|
+
self._avatar_data_path: Optional[Path] = None
|
56
|
+
|
57
|
+
@property
|
58
|
+
def data(self) -> Optional[AvatarData]:
|
59
|
+
if self._avatar_data_path is None:
|
60
|
+
return None
|
61
|
+
data = AvatarData()
|
62
|
+
data.set_value(self._avatar_data_path.read_bytes())
|
63
|
+
return data
|
64
|
+
|
65
|
+
@staticmethod
|
66
|
+
def _sha(b: bytes):
|
67
|
+
return hashlib.sha1(b).hexdigest()
|
68
|
+
|
69
|
+
def _get_weak_unique_id(self, avatar: AvatarType):
|
70
|
+
if isinstance(avatar, str):
|
71
|
+
return avatar # url
|
72
|
+
elif isinstance(avatar, bytes):
|
73
|
+
return self._sha(avatar)
|
74
|
+
elif isinstance(avatar, Path):
|
75
|
+
return self._sha(avatar.read_bytes())
|
76
|
+
|
77
|
+
@staticmethod
|
78
|
+
async def _get_image(avatar: AvatarType) -> PILImage:
|
79
|
+
if isinstance(avatar, str):
|
80
|
+
# async with aiohttp.ClientSession() as session:
|
81
|
+
async with avatar_cache.http.get(avatar) as response:
|
82
|
+
return Image.open(io.BytesIO(await response.read()))
|
83
|
+
elif isinstance(avatar, bytes):
|
84
|
+
return Image.open(io.BytesIO(avatar))
|
85
|
+
elif isinstance(avatar, Path):
|
86
|
+
return Image.open(avatar)
|
87
|
+
else:
|
88
|
+
raise TypeError("Avatar must be bytes, a Path or a str (URL)", avatar)
|
89
|
+
|
90
|
+
async def set_avatar(
|
91
|
+
self, avatar: AvatarType, unique_id: Optional[LegacyFileIdType] = None
|
92
|
+
):
|
93
|
+
if unique_id is None:
|
94
|
+
if isinstance(avatar, str):
|
95
|
+
await self._set_avatar_from_url_alone(avatar)
|
96
|
+
return
|
97
|
+
unique_id = self._get_weak_unique_id(avatar)
|
98
|
+
|
99
|
+
await self._set_avatar_from_unique_id(avatar, unique_id)
|
100
|
+
|
101
|
+
async def _set_avatar_from_unique_id(
|
102
|
+
self, avatar: AvatarType, unique_id: LegacyFileIdType
|
103
|
+
):
|
104
|
+
cached_avatar = avatar_cache.get(unique_id)
|
105
|
+
if cached_avatar:
|
106
|
+
# this shouldn't be necessary but here to re-use avatars downloaded
|
107
|
+
# before the change introducing the JID to unique ID mapping
|
108
|
+
avatar_cache.store_jid(self.jid, unique_id)
|
109
|
+
else:
|
110
|
+
img = await self._get_image(avatar)
|
111
|
+
cached_avatar = await avatar_cache.convert_and_store(
|
112
|
+
img, unique_id, self.jid
|
113
|
+
)
|
114
|
+
|
115
|
+
self._set_avatar_from_cache(cached_avatar)
|
116
|
+
|
117
|
+
async def _set_avatar_from_url_alone(self, url: str):
|
118
|
+
cached_avatar = await avatar_cache.get_avatar_from_url_alone(url, self.jid)
|
119
|
+
self._set_avatar_from_cache(cached_avatar)
|
120
|
+
|
121
|
+
def _set_avatar_from_cache(self, cached_avatar: CachedAvatar):
|
122
|
+
metadata = AvatarMetadata()
|
123
|
+
self.id = cached_avatar.hash
|
124
|
+
metadata.add_info(
|
125
|
+
id=cached_avatar.hash,
|
126
|
+
itype="image/png",
|
127
|
+
ibytes=cached_avatar.path.stat().st_size,
|
128
|
+
height=str(cached_avatar.height),
|
129
|
+
width=str(cached_avatar.width),
|
130
|
+
)
|
131
|
+
self.metadata = metadata
|
132
|
+
self._avatar_data_path = cached_avatar.path
|
133
|
+
|
134
|
+
@staticmethod
|
135
|
+
def from_db(jid: JID, user: Optional[GatewayUser] = None) -> Optional["PepAvatar"]:
|
136
|
+
cached_id = db.avatar_get(jid)
|
137
|
+
if cached_id is None:
|
138
|
+
return None
|
139
|
+
item = PepAvatar(jid)
|
140
|
+
cached_avatar = avatar_cache.get(cached_id)
|
141
|
+
if cached_avatar is None:
|
142
|
+
raise XMPPError("internal-server-error")
|
143
|
+
item._set_avatar_from_cache(cached_avatar)
|
144
|
+
return item
|
145
|
+
|
146
|
+
def to_db(self, jid: JID, user=None):
|
147
|
+
cached_id = avatar_cache.get_cached_id_for(jid)
|
148
|
+
if cached_id is None:
|
149
|
+
log.warning("Could not store avatar for %s", jid)
|
150
|
+
return
|
151
|
+
db.avatar_store(jid, cached_id)
|
152
|
+
|
153
|
+
@staticmethod
|
154
|
+
def remove_from_db(jid: JID):
|
155
|
+
db.avatar_delete(jid)
|
156
|
+
|
157
|
+
|
158
|
+
class PepNick(PepItem):
|
159
|
+
def __init__(self, nick: Optional[str] = None):
|
160
|
+
nickname = UserNick()
|
161
|
+
if nick is not None:
|
162
|
+
nickname["nick"] = nick
|
163
|
+
self.nick = nickname
|
164
|
+
self.__nick_str = nick
|
165
|
+
|
166
|
+
@staticmethod
|
167
|
+
def from_db(jid: JID, user: Optional[GatewayUser] = None) -> Optional["PepNick"]:
|
168
|
+
if user is None:
|
169
|
+
raise XMPPError("not-allowed")
|
170
|
+
nick = db.nick_get(jid, user)
|
171
|
+
if nick is None:
|
172
|
+
return None
|
173
|
+
return PepNick(nick)
|
174
|
+
|
175
|
+
def to_db(self, jid: JID, user: Optional[GatewayUser] = None):
|
176
|
+
if user is None:
|
177
|
+
raise XMPPError("not-allowed")
|
178
|
+
db.nick_store(jid, str(self.__nick_str), user)
|
179
|
+
|
180
|
+
|
181
|
+
class PubSubComponent(NamedLockMixin, BasePlugin):
|
182
|
+
xmpp: "BaseGateway"
|
183
|
+
|
184
|
+
name = "pubsub"
|
185
|
+
description = "Pubsub component"
|
186
|
+
dependencies = {
|
187
|
+
"xep_0030",
|
188
|
+
"xep_0060",
|
189
|
+
# "xep_0084",
|
190
|
+
"xep_0115",
|
191
|
+
"xep_0163",
|
192
|
+
}
|
193
|
+
default_config = {"component_name": None}
|
194
|
+
component_name: str
|
195
|
+
|
196
|
+
def __init__(self, *a, **kw):
|
197
|
+
super(PubSubComponent, self).__init__(*a, **kw)
|
198
|
+
register_stanza_plugin(EventItem, UserNick)
|
199
|
+
|
200
|
+
def plugin_init(self):
|
201
|
+
self.xmpp.register_handler(
|
202
|
+
CoroutineCallback(
|
203
|
+
"pubsub_get_avatar_data",
|
204
|
+
StanzaPath(f"iq@type=get/pubsub/items@node={AvatarData.namespace}"),
|
205
|
+
self._get_avatar_data, # type:ignore
|
206
|
+
)
|
207
|
+
)
|
208
|
+
self.xmpp.register_handler(
|
209
|
+
CoroutineCallback(
|
210
|
+
"pubsub_get_avatar_metadata",
|
211
|
+
StanzaPath(f"iq@type=get/pubsub/items@node={AvatarMetadata.namespace}"),
|
212
|
+
self._get_avatar_metadata, # type:ignore
|
213
|
+
)
|
214
|
+
)
|
215
|
+
self.xmpp.register_handler(
|
216
|
+
CoroutineCallback(
|
217
|
+
"pubsub_get_vcard",
|
218
|
+
StanzaPath(f"iq@type=get/pubsub/items@node={VCARD4_NAMESPACE}"),
|
219
|
+
self._get_vcard, # type:ignore
|
220
|
+
)
|
221
|
+
)
|
222
|
+
self.xmpp.add_event_handler("presence_available", self._on_presence_available)
|
223
|
+
|
224
|
+
disco = self.xmpp.plugin["xep_0030"]
|
225
|
+
disco.add_identity("pubsub", "pep", self.component_name)
|
226
|
+
disco.add_identity("account", "registered", self.component_name)
|
227
|
+
disco.add_feature("http://jabber.org/protocol/pubsub#event")
|
228
|
+
disco.add_feature("http://jabber.org/protocol/pubsub#retrieve-items")
|
229
|
+
disco.add_feature("http://jabber.org/protocol/pubsub#persistent-items")
|
230
|
+
|
231
|
+
async def _on_presence_available(self, p: Presence):
|
232
|
+
if p.get_plugin("muc_join", check=True) is not None:
|
233
|
+
log.debug("Ignoring MUC presence here")
|
234
|
+
return
|
235
|
+
|
236
|
+
from_ = p.get_from()
|
237
|
+
ver_string = p["caps"]["ver"]
|
238
|
+
info = None
|
239
|
+
|
240
|
+
to = p.get_to()
|
241
|
+
|
242
|
+
# we don't want to push anything for contacts that are not in the user's roster
|
243
|
+
if to != self.xmpp.boundjid.bare:
|
244
|
+
session = self.xmpp.get_session_from_stanza(p)
|
245
|
+
|
246
|
+
if session is None:
|
247
|
+
return
|
248
|
+
|
249
|
+
await session.contacts.ready
|
250
|
+
try:
|
251
|
+
contact = await session.contacts.by_jid(to)
|
252
|
+
except XMPPError as e:
|
253
|
+
log.debug(
|
254
|
+
"Could not determine if %s was added to the roster: %s", to, e
|
255
|
+
)
|
256
|
+
return
|
257
|
+
except Exception as e:
|
258
|
+
log.warning("Could not determine if %s was added to the roster.", to)
|
259
|
+
log.exception(e)
|
260
|
+
return
|
261
|
+
if not contact.is_friend:
|
262
|
+
return
|
263
|
+
|
264
|
+
if ver_string:
|
265
|
+
info = await self.xmpp.plugin["xep_0115"].get_caps(from_)
|
266
|
+
if info is None:
|
267
|
+
async with self.lock(from_):
|
268
|
+
iq = await self.xmpp.plugin["xep_0030"].get_info(from_)
|
269
|
+
info = iq["disco_info"]
|
270
|
+
features = info["features"]
|
271
|
+
if AvatarMetadata.namespace + "+notify" in features:
|
272
|
+
try:
|
273
|
+
pep_avatar = await self._get_authorized_avatar(p)
|
274
|
+
except XMPPError:
|
275
|
+
pass
|
276
|
+
else:
|
277
|
+
if pep_avatar.metadata is None:
|
278
|
+
raise XMPPError("internal-server-error", "Avatar but no metadata?")
|
279
|
+
await self._broadcast(
|
280
|
+
data=pep_avatar.metadata,
|
281
|
+
from_=p.get_to(),
|
282
|
+
to=from_,
|
283
|
+
id=pep_avatar.metadata["info"]["id"],
|
284
|
+
)
|
285
|
+
if UserNick.namespace + "+notify" in features:
|
286
|
+
try:
|
287
|
+
pep_nick = await self._get_authorized_nick(p)
|
288
|
+
except XMPPError:
|
289
|
+
pass
|
290
|
+
else:
|
291
|
+
await self._broadcast(data=pep_nick.nick, from_=p.get_to(), to=from_)
|
292
|
+
|
293
|
+
if VCARD4_NAMESPACE + "+notify" in features:
|
294
|
+
await self.broadcast_vcard_event(p.get_to(), to=from_)
|
295
|
+
|
296
|
+
async def broadcast_vcard_event(self, from_, to):
|
297
|
+
item = Item()
|
298
|
+
item.namespace = VCARD4_NAMESPACE
|
299
|
+
item["id"] = "current"
|
300
|
+
vcard: VCard4 = await self.xmpp["xep_0292_provider"].get_vcard(from_, to)
|
301
|
+
# The vcard content should NOT be in this event according to the spec:
|
302
|
+
# https://xmpp.org/extensions/xep-0292.html#sect-idm45669698174224
|
303
|
+
# but movim expects it to be here, and I guess
|
304
|
+
|
305
|
+
log.debug("Broadcast vcard4 event: %s", vcard)
|
306
|
+
await self._broadcast(
|
307
|
+
data=vcard,
|
308
|
+
from_=JID(from_).bare,
|
309
|
+
to=to,
|
310
|
+
id="current",
|
311
|
+
node=VCARD4_NAMESPACE,
|
312
|
+
)
|
313
|
+
|
314
|
+
async def _get_authorized_item(
|
315
|
+
self, cls: Type[PepItemType], stanza: Union[Iq, Presence]
|
316
|
+
) -> PepItemType:
|
317
|
+
sto = stanza.get_to()
|
318
|
+
user = user_store.get_by_jid(stanza.get_from())
|
319
|
+
item = cls.from_db(sto, user)
|
320
|
+
if item is None:
|
321
|
+
raise XMPPError("item-not-found")
|
322
|
+
|
323
|
+
if sto != self.xmpp.boundjid.bare:
|
324
|
+
session = self.xmpp.get_session_from_stanza(stanza)
|
325
|
+
await session.contacts.by_jid(sto)
|
326
|
+
|
327
|
+
return item # type:ignore
|
328
|
+
|
329
|
+
async def _get_authorized_avatar(self, stanza: Union[Iq, Presence]) -> PepAvatar:
|
330
|
+
return await self._get_authorized_item(PepAvatar, stanza)
|
331
|
+
|
332
|
+
async def _get_authorized_nick(self, stanza: Union[Iq, Presence]) -> PepNick:
|
333
|
+
return await self._get_authorized_item(PepNick, stanza)
|
334
|
+
|
335
|
+
async def _get_avatar_data(self, iq: Iq):
|
336
|
+
pep_avatar = await self._get_authorized_avatar(iq)
|
337
|
+
|
338
|
+
requested_items = iq["pubsub"]["items"]
|
339
|
+
if len(requested_items) == 0:
|
340
|
+
self._reply_with_payload(iq, pep_avatar.data, pep_avatar.id)
|
341
|
+
else:
|
342
|
+
for item in requested_items:
|
343
|
+
if item["id"] == pep_avatar.id:
|
344
|
+
self._reply_with_payload(iq, pep_avatar.data, pep_avatar.id)
|
345
|
+
return
|
346
|
+
else:
|
347
|
+
raise XMPPError("item-not-found")
|
348
|
+
|
349
|
+
async def _get_avatar_metadata(self, iq: Iq):
|
350
|
+
pep_avatar = await self._get_authorized_avatar(iq)
|
351
|
+
|
352
|
+
requested_items = iq["pubsub"]["items"]
|
353
|
+
if len(requested_items) == 0:
|
354
|
+
self._reply_with_payload(iq, pep_avatar.metadata, pep_avatar.id)
|
355
|
+
else:
|
356
|
+
for item in requested_items:
|
357
|
+
if item["id"] == pep_avatar.id:
|
358
|
+
self._reply_with_payload(iq, pep_avatar.metadata, pep_avatar.id)
|
359
|
+
return
|
360
|
+
else:
|
361
|
+
raise XMPPError("item-not-found")
|
362
|
+
|
363
|
+
async def _get_vcard(self, iq: Iq):
|
364
|
+
# this is not the proper way that clients should retrieve VCards, but
|
365
|
+
# gajim does it this way.
|
366
|
+
# https://xmpp.org/extensions/xep-0292.html#sect-idm45669698174224
|
367
|
+
vcard: VCard4 = await self.xmpp["xep_0292_provider"].get_vcard(
|
368
|
+
iq.get_to().bare, iq.get_from().bare
|
369
|
+
)
|
370
|
+
log.debug("VCARD: %s -- %s -- %s", iq.get_to().bare, iq.get_from().bare, vcard)
|
371
|
+
if vcard is None:
|
372
|
+
raise XMPPError("item-not-found")
|
373
|
+
self._reply_with_payload(iq, vcard, "current", VCARD4_NAMESPACE)
|
374
|
+
|
375
|
+
@staticmethod
|
376
|
+
def get_avatar(jid: JID):
|
377
|
+
return PepAvatar.from_db(jid)
|
378
|
+
|
379
|
+
@staticmethod
|
380
|
+
def _reply_with_payload(
|
381
|
+
iq: Iq,
|
382
|
+
payload: Optional[Union[AvatarMetadata, AvatarData, VCard4]],
|
383
|
+
id_: Optional[str],
|
384
|
+
namespace: Optional[str] = None,
|
385
|
+
):
|
386
|
+
result = iq.reply()
|
387
|
+
item = Item()
|
388
|
+
if payload:
|
389
|
+
item.set_payload(payload.xml)
|
390
|
+
item["id"] = id_
|
391
|
+
result["pubsub"]["items"]["node"] = (
|
392
|
+
namespace if namespace else payload.namespace
|
393
|
+
)
|
394
|
+
result["pubsub"]["items"].append(item)
|
395
|
+
result.send()
|
396
|
+
|
397
|
+
async def _broadcast(self, data, from_: JidStr, to: OptJidStr = None, **kwargs):
|
398
|
+
from_ = JID(from_)
|
399
|
+
if from_ != self.xmpp.boundjid.bare and to is not None:
|
400
|
+
to = JID(to)
|
401
|
+
session = self.xmpp.get_session_from_jid(to)
|
402
|
+
if session is None:
|
403
|
+
return
|
404
|
+
await session.ready
|
405
|
+
try:
|
406
|
+
entity = await session.get_contact_or_group_or_participant(from_)
|
407
|
+
except ContactIsUser:
|
408
|
+
return
|
409
|
+
if isinstance(entity, LegacyContact) and not entity.is_friend:
|
410
|
+
return
|
411
|
+
|
412
|
+
item = EventItem()
|
413
|
+
if data:
|
414
|
+
item.set_payload(data.xml)
|
415
|
+
for k, v in kwargs.items():
|
416
|
+
item[k] = v
|
417
|
+
|
418
|
+
items = EventItems()
|
419
|
+
items.append(item)
|
420
|
+
items["node"] = kwargs.get("node") or data.namespace
|
421
|
+
|
422
|
+
event = Event()
|
423
|
+
event.append(items)
|
424
|
+
|
425
|
+
msg = self.xmpp.Message()
|
426
|
+
msg.set_type("headline")
|
427
|
+
msg.set_from(from_)
|
428
|
+
msg.append(event)
|
429
|
+
|
430
|
+
if to is None:
|
431
|
+
for u in user_store.get_all():
|
432
|
+
new_msg = copy(msg)
|
433
|
+
new_msg.set_to(u.bare_jid)
|
434
|
+
new_msg.send()
|
435
|
+
else:
|
436
|
+
msg.set_to(to)
|
437
|
+
msg.send()
|
438
|
+
|
439
|
+
async def set_avatar(
|
440
|
+
self,
|
441
|
+
jid: JidStr,
|
442
|
+
avatar: Optional[AvatarType] = None,
|
443
|
+
broadcast_to: OptJidStr = None,
|
444
|
+
unique_id=None,
|
445
|
+
broadcast=True,
|
446
|
+
):
|
447
|
+
jid = JID(jid)
|
448
|
+
if avatar is None:
|
449
|
+
PepAvatar.remove_from_db(jid)
|
450
|
+
await self._broadcast(AvatarMetadata(), jid, broadcast_to)
|
451
|
+
avatar_cache.delete_jid(jid)
|
452
|
+
else:
|
453
|
+
pep_avatar = PepAvatar(jid)
|
454
|
+
try:
|
455
|
+
await pep_avatar.set_avatar(avatar, unique_id)
|
456
|
+
except (UnidentifiedImageError, FileNotFoundError) as e:
|
457
|
+
log.warning("Failed to set avatar for %s: %r", self, e)
|
458
|
+
return
|
459
|
+
pep_avatar.to_db(jid)
|
460
|
+
if pep_avatar.metadata is None:
|
461
|
+
raise RuntimeError
|
462
|
+
if not broadcast:
|
463
|
+
return
|
464
|
+
await self._broadcast(
|
465
|
+
pep_avatar.metadata,
|
466
|
+
jid,
|
467
|
+
broadcast_to,
|
468
|
+
id=pep_avatar.metadata["info"]["id"],
|
469
|
+
)
|
470
|
+
|
471
|
+
async def set_avatar_from_cache(
|
472
|
+
self, jid: JID, send_empty: bool, broadcast_to: OptJidStr = None, broadcast=True
|
473
|
+
):
|
474
|
+
uid = avatar_cache.get_cached_id_for(jid)
|
475
|
+
if uid is None:
|
476
|
+
if not send_empty:
|
477
|
+
return
|
478
|
+
self.xmpp.loop.create_task(
|
479
|
+
self.set_avatar(jid, None, broadcast_to, uid, broadcast)
|
480
|
+
)
|
481
|
+
return
|
482
|
+
cached_avatar = avatar_cache.get(str(uid))
|
483
|
+
if cached_avatar is None:
|
484
|
+
# should not happen but well…
|
485
|
+
log.warning(
|
486
|
+
"Something is wrong with the avatar, %s won't have an "
|
487
|
+
"avatar because avatar not found in cache",
|
488
|
+
jid,
|
489
|
+
)
|
490
|
+
return
|
491
|
+
self.xmpp.loop.create_task(
|
492
|
+
self.set_avatar(jid, cached_avatar.path, broadcast_to, uid, broadcast)
|
493
|
+
)
|
494
|
+
|
495
|
+
def set_nick(
|
496
|
+
self,
|
497
|
+
user: GatewayUser,
|
498
|
+
jid: JidStr,
|
499
|
+
nick: Optional[str] = None,
|
500
|
+
):
|
501
|
+
jid = JID(jid)
|
502
|
+
nickname = PepNick(nick)
|
503
|
+
nickname.to_db(jid, user)
|
504
|
+
log.debug("New nickname: %s", nickname.nick)
|
505
|
+
self.xmpp.loop.create_task(self._broadcast(nickname.nick, jid, user.bare_jid))
|
506
|
+
|
507
|
+
async def broadcast_all(self, from_: JID, to: JID):
|
508
|
+
"""
|
509
|
+
Force push avatar and nick for a stored JID.
|
510
|
+
"""
|
511
|
+
a = PepAvatar.from_db(from_)
|
512
|
+
if a:
|
513
|
+
if a.metadata:
|
514
|
+
await self._broadcast(
|
515
|
+
a.metadata, from_, to, id=a.metadata["info"]["id"]
|
516
|
+
)
|
517
|
+
else:
|
518
|
+
log.warning("No metadata associated to this cached avatar?!")
|
519
|
+
n = PepNick.from_db(from_, user_store.get_by_jid(to))
|
520
|
+
if n:
|
521
|
+
await self._broadcast(n.nick, from_, to)
|
522
|
+
|
523
|
+
|
524
|
+
log = logging.getLogger(__name__)
|
525
|
+
register_plugin(PubSubComponent)
|