slidge 0.2.0a0__tar.gz → 0.2.0a2__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {slidge-0.2.0a0 → slidge-0.2.0a2}/PKG-INFO +2 -4
- {slidge-0.2.0a0 → slidge-0.2.0a2}/pyproject.toml +2 -2
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/__version__.py +1 -1
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/command/admin.py +1 -1
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/command/user.py +0 -1
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/contact/contact.py +117 -35
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/contact/roster.py +79 -19
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/core/config.py +1 -4
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/core/gateway/base.py +9 -2
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/core/gateway/caps.py +7 -5
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/core/gateway/muc_admin.py +1 -1
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/core/gateway/session_dispatcher.py +20 -6
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/core/gateway/vcard_temp.py +1 -1
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/core/mixins/attachment.py +17 -4
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/core/mixins/avatar.py +26 -9
- slidge-0.2.0a2/slidge/core/mixins/db.py +18 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/core/mixins/disco.py +0 -10
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/core/mixins/message.py +7 -1
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/core/mixins/presence.py +6 -3
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/core/pubsub.py +7 -15
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/core/session.py +6 -3
- slidge-0.2.0a2/slidge/db/alembic/versions/2461390c0af2_store_contacts_caps_verstring_in_db.py +36 -0
- slidge-0.2.0a2/slidge/db/alembic/versions/2b1f45ab7379_store_room_subject_setter_by_nickname.py +41 -0
- slidge-0.2.0a2/slidge/db/alembic/versions/82a4af84b679_add_muc_history_filled.py +48 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +11 -2
- slidge-0.2.0a2/slidge/db/alembic/versions/b64b1a793483_add_source_and_legacy_id_for_archived_.py +48 -0
- slidge-0.2.0a2/slidge/db/alembic/versions/c4a8ec35a0e8_per_room_user_nick.py +34 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/db/avatar.py +20 -9
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/db/models.py +16 -6
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/db/store.py +219 -115
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/group/archive.py +46 -1
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/group/bookmarks.py +17 -5
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/group/participant.py +10 -3
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/group/room.py +191 -127
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/main.py +3 -3
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/slixfix/xep_0292/vcard4.py +2 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/util/archive_msg.py +2 -1
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/util/test.py +54 -4
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/util/types.py +5 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/util/util.py +22 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/LICENSE +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/README.md +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/__init__.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/__main__.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/command/__init__.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/command/adhoc.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/command/base.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/command/categories.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/command/chat_command.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/command/register.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/contact/__init__.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/core/__init__.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/core/gateway/__init__.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/core/gateway/delivery_receipt.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/core/gateway/disco.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/core/gateway/mam.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/core/gateway/ping.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/core/gateway/presence.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/core/gateway/registration.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/core/gateway/search.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/core/mixins/__init__.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/core/mixins/base.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/core/mixins/lock.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/core/mixins/message_maker.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/core/mixins/recipient.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/db/__init__.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/db/alembic/env.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/db/alembic/script.py.mako +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/db/alembic/versions/09f27f098baa_add_missing_attributes_in_room.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/db/alembic/versions/29f5280c61aa_store_subject_setter_in_room.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/db/alembic/versions/8d2ced764698_rely_on_db_to_store_contacts_rooms_and_.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/db/alembic/versions/b33993e87db3_move_everything_to_persistent_db.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/db/alembic/versions/e91195719c2c_store_users_avatars_persistently.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/db/meta.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/group/__init__.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/migration.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/py.typed +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/slixfix/__init__.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/slixfix/link_preview/__init__.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/slixfix/link_preview/link_preview.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/slixfix/link_preview/stanza.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/slixfix/roster.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/slixfix/xep_0077/__init__.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/slixfix/xep_0077/register.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/slixfix/xep_0077/stanza.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/slixfix/xep_0100/__init__.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/slixfix/xep_0100/gateway.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/slixfix/xep_0100/stanza.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/slixfix/xep_0153/__init__.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/slixfix/xep_0153/stanza.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/slixfix/xep_0153/vcard_avatar.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/slixfix/xep_0264/__init__.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/slixfix/xep_0264/stanza.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/slixfix/xep_0264/thumbnail.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/slixfix/xep_0292/__init__.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/slixfix/xep_0313/__init__.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/slixfix/xep_0313/mam.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/slixfix/xep_0313/stanza.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/slixfix/xep_0317/__init__.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/slixfix/xep_0317/hats.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/slixfix/xep_0317/stanza.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/slixfix/xep_0356_old/__init__.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/slixfix/xep_0356_old/privilege.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/slixfix/xep_0356_old/stanza.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/slixfix/xep_0424/__init__.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/slixfix/xep_0424/retraction.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/slixfix/xep_0424/stanza.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/slixfix/xep_0490/__init__.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/slixfix/xep_0490/mds.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/slixfix/xep_0490/stanza.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/util/__init__.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/util/conf.py +0 -0
- {slidge-0.2.0a0 → slidge-0.2.0a2}/slidge/util/db.py +0 -0
@@ -1,16 +1,14 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: slidge
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.0a2
|
4
4
|
Summary: XMPP bridging framework
|
5
5
|
Home-page: https://sr.ht/~nicoco/slidge/
|
6
6
|
License: AGPL-3.0-or-later
|
7
7
|
Author: Nicolas Cedilnik
|
8
8
|
Author-email: nicoco@nicoco.fr
|
9
|
-
Requires-Python: >=3.
|
9
|
+
Requires-Python: >=3.11,<4.0
|
10
10
|
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
12
|
-
Classifier: Programming Language :: Python :: 3.9
|
13
|
-
Classifier: Programming Language :: Python :: 3.10
|
14
12
|
Classifier: Programming Language :: Python :: 3.11
|
15
13
|
Requires-Dist: ConfigArgParse (>=1.5.3,<2.0.0)
|
16
14
|
Requires-Dist: Pillow (>=10,<11)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "slidge"
|
3
|
-
version = "0.2.
|
3
|
+
version = "0.2.0alpha2"
|
4
4
|
description = "XMPP bridging framework"
|
5
5
|
authors = ["Nicolas Cedilnik <nicoco@nicoco.fr>"]
|
6
6
|
readme = "README.md"
|
@@ -11,7 +11,7 @@ documentation = "https://slidge.im/"
|
|
11
11
|
include = ["slidge/util/schema.sql"]
|
12
12
|
|
13
13
|
[tool.poetry.dependencies]
|
14
|
-
python = "^3.
|
14
|
+
python = "^3.11"
|
15
15
|
qrcode = "^7.4.1"
|
16
16
|
Pillow = "^10"
|
17
17
|
aiohttp = {version = "^3.8.3", extras = ["speedups"]}
|
@@ -55,7 +55,7 @@ class SlidgeInfo(AdminCommand):
|
|
55
55
|
async def run(self, _session, _ifrom, *_):
|
56
56
|
from slidge.__version__ import __version__
|
57
57
|
|
58
|
-
start = self.xmpp.datetime_started
|
58
|
+
start = self.xmpp.datetime_started # type:ignore
|
59
59
|
uptime = datetime.now() - start
|
60
60
|
|
61
61
|
if uptime.days:
|
@@ -131,7 +131,6 @@ class ListContacts(Command):
|
|
131
131
|
self, session: Optional[AnyBaseSession], _ifrom: JID, *_
|
132
132
|
) -> TableResult:
|
133
133
|
assert session is not None
|
134
|
-
await session.contacts.fill()
|
135
134
|
contacts = sorted(
|
136
135
|
session.contacts, key=lambda c: c.name.casefold() if c.name else ""
|
137
136
|
)
|
@@ -11,6 +11,7 @@ from slixmpp.types import MessageTypes
|
|
11
11
|
|
12
12
|
from ..core import config
|
13
13
|
from ..core.mixins import AvatarMixin, FullCarbonMixin, StoredAttributeMixin
|
14
|
+
from ..core.mixins.db import UpdateInfoMixin
|
14
15
|
from ..core.mixins.disco import ContactAccountDiscoMixin
|
15
16
|
from ..core.mixins.recipient import ReactionRecipientMixin, ThreadRecipientMixin
|
16
17
|
from ..db.models import Contact
|
@@ -30,6 +31,7 @@ class LegacyContact(
|
|
30
31
|
FullCarbonMixin,
|
31
32
|
ReactionRecipientMixin,
|
32
33
|
ThreadRecipientMixin,
|
34
|
+
UpdateInfoMixin,
|
33
35
|
metaclass=SubclassableOnce,
|
34
36
|
):
|
35
37
|
"""
|
@@ -82,7 +84,6 @@ class LegacyContact(
|
|
82
84
|
STRIP_SHORT_DELAY = True
|
83
85
|
_NON_FRIEND_PRESENCES_FILTER = {"subscribe", "unsubscribed"}
|
84
86
|
|
85
|
-
_avatar_pubsub_broadcast = True
|
86
87
|
_avatar_bare_jid = True
|
87
88
|
|
88
89
|
INVITATION_RECIPIENT = True
|
@@ -119,9 +120,11 @@ class LegacyContact(
|
|
119
120
|
self.xmpp = session.xmpp
|
120
121
|
self.jid = JID(self.jid_username + "@" + self.xmpp.boundjid.bare)
|
121
122
|
self.jid.resource = self.RESOURCE
|
122
|
-
self.log = logging.getLogger(
|
123
|
+
self.log = logging.getLogger(self.jid.bare)
|
124
|
+
self._set_logger_name()
|
123
125
|
self._is_friend: bool = False
|
124
|
-
self.
|
126
|
+
self._added_to_roster = False
|
127
|
+
self._caps_ver: str | None = None
|
125
128
|
|
126
129
|
@property
|
127
130
|
def is_friend(self):
|
@@ -132,12 +135,34 @@ class LegacyContact(
|
|
132
135
|
if value == self._is_friend:
|
133
136
|
return
|
134
137
|
self._is_friend = value
|
138
|
+
if self._updating_info:
|
139
|
+
return
|
140
|
+
self.__ensure_pk()
|
135
141
|
assert self.contact_pk is not None
|
136
142
|
self.xmpp.store.contacts.set_friend(self.contact_pk, value)
|
137
143
|
|
144
|
+
@property
|
145
|
+
def added_to_roster(self):
|
146
|
+
return self._added_to_roster
|
147
|
+
|
148
|
+
@added_to_roster.setter
|
149
|
+
def added_to_roster(self, value: bool):
|
150
|
+
if value == self._added_to_roster:
|
151
|
+
return
|
152
|
+
self._added_to_roster = value
|
153
|
+
if self._updating_info:
|
154
|
+
return
|
155
|
+
if self.contact_pk is None:
|
156
|
+
# during LegacyRoster.fill()
|
157
|
+
return
|
158
|
+
self.xmpp.store.contacts.set_added_to_roster(self.contact_pk, value)
|
159
|
+
|
138
160
|
@property
|
139
161
|
def participants(self) -> list["LegacyParticipant"]:
|
140
|
-
|
162
|
+
if self.contact_pk is None:
|
163
|
+
return []
|
164
|
+
|
165
|
+
self.__ensure_pk()
|
141
166
|
from ..group.participant import LegacyParticipant
|
142
167
|
|
143
168
|
return [
|
@@ -151,8 +176,25 @@ class LegacyContact(
|
|
151
176
|
def user_jid(self):
|
152
177
|
return self.session.user_jid
|
153
178
|
|
179
|
+
def _set_logger_name(self):
|
180
|
+
self.log.name = f"{self.user_jid.bare}:contact:{self}"
|
181
|
+
|
154
182
|
def __repr__(self):
|
155
|
-
return f"<Contact '{self.
|
183
|
+
return f"<Contact #{self.contact_pk} '{self.name}' ({self.legacy_id} - {self.jid.local})'>"
|
184
|
+
|
185
|
+
def __ensure_pk(self):
|
186
|
+
if self.contact_pk is not None:
|
187
|
+
return
|
188
|
+
with self.xmpp.store.session() as orm:
|
189
|
+
orm.commit()
|
190
|
+
stored = self.xmpp.store.contacts.get_by_legacy_id(
|
191
|
+
self.user_pk, str(self.legacy_id)
|
192
|
+
)
|
193
|
+
if stored is None:
|
194
|
+
self.log.error("Cannot find our primary key!", stack_info=True)
|
195
|
+
raise RuntimeError("Cannot find our primary key!")
|
196
|
+
self.contact_pk = stored.id
|
197
|
+
assert self.contact_pk is not None
|
156
198
|
|
157
199
|
def __get_subscription_string(self):
|
158
200
|
if self.is_friend:
|
@@ -198,7 +240,7 @@ class LegacyContact(
|
|
198
240
|
self._privileged_send(stanza)
|
199
241
|
return stanza # type:ignore
|
200
242
|
|
201
|
-
if isinstance(stanza, Presence):
|
243
|
+
if not self._updating_info and isinstance(stanza, Presence):
|
202
244
|
self.__propagate_to_participants(stanza)
|
203
245
|
if (
|
204
246
|
not self.is_friend
|
@@ -209,7 +251,12 @@ class LegacyContact(
|
|
209
251
|
n = self.xmpp.plugin["xep_0172"].stanza.UserNick()
|
210
252
|
n["nick"] = self.name
|
211
253
|
stanza.append(n)
|
212
|
-
if
|
254
|
+
if (
|
255
|
+
not self._updating_info
|
256
|
+
and self.xmpp.MARK_ALL_MESSAGES
|
257
|
+
and is_markable(stanza)
|
258
|
+
):
|
259
|
+
self.__ensure_pk()
|
213
260
|
assert self.contact_pk is not None
|
214
261
|
self.xmpp.store.contacts.add_to_sent(self.contact_pk, stanza["id"])
|
215
262
|
stanza["to"] = self.user_jid
|
@@ -230,6 +277,7 @@ class LegacyContact(
|
|
230
277
|
:param horizon_xmpp_id: The latest message
|
231
278
|
:return: A list of XMPP ids or None if horizon_xmpp_id was not found
|
232
279
|
"""
|
280
|
+
self.__ensure_pk()
|
233
281
|
assert self.contact_pk is not None
|
234
282
|
return self.xmpp.store.contacts.pop_sent_up_to(self.contact_pk, horizon_xmpp_id)
|
235
283
|
|
@@ -244,21 +292,35 @@ class LegacyContact(
|
|
244
292
|
def name(self, n: Optional[str]):
|
245
293
|
if self._name == n:
|
246
294
|
return
|
295
|
+
self._name = n
|
296
|
+
self._set_logger_name()
|
297
|
+
if self.is_friend and self.added_to_roster:
|
298
|
+
self.xmpp.pubsub.broadcast_nick(
|
299
|
+
user_jid=self.user_jid, jid=self.jid.bare, nick=n
|
300
|
+
)
|
301
|
+
if self._updating_info:
|
302
|
+
# means we're in update_info(), so no participants, and no need
|
303
|
+
# to write to DB now, it will be called in Roster.__finish_init_contact
|
304
|
+
return
|
247
305
|
for p in self.participants:
|
248
306
|
p.nickname = n
|
249
|
-
self.
|
307
|
+
self.__ensure_pk()
|
250
308
|
assert self.contact_pk is not None
|
251
309
|
self.xmpp.store.contacts.update_nick(self.contact_pk, n)
|
252
|
-
self.xmpp.pubsub.broadcast_nick(
|
253
|
-
user_jid=self.user_jid, jid=self.jid.bare, nick=n
|
254
|
-
)
|
255
310
|
|
256
|
-
def _get_cached_avatar_id(self):
|
257
|
-
|
311
|
+
def _get_cached_avatar_id(self) -> Optional[str]:
|
312
|
+
if self.contact_pk is None:
|
313
|
+
return None
|
258
314
|
return self.xmpp.store.contacts.get_avatar_legacy_id(self.contact_pk)
|
259
315
|
|
260
316
|
def _post_avatar_update(self):
|
261
|
-
|
317
|
+
if self._updating_info:
|
318
|
+
return
|
319
|
+
if self.contact_pk is None:
|
320
|
+
# happens in LegacyRoster.fill(), the contact primary key is not
|
321
|
+
# set yet, but this will eventually be called in LegacyRoster.__finish_init_contact
|
322
|
+
self.log.debug("Not setting avatar PK")
|
323
|
+
return
|
262
324
|
self.xmpp.store.contacts.set_avatar(self.contact_pk, self._avatar_pk)
|
263
325
|
for p in self.participants:
|
264
326
|
self.log.debug("Propagating new avatar to %s", p.muc)
|
@@ -313,6 +375,15 @@ class LegacyContact(
|
|
313
375
|
|
314
376
|
self.xmpp.vcard.set_vcard(self.jid.bare, vcard, {self.user_jid.bare})
|
315
377
|
|
378
|
+
def get_roster_item(self):
|
379
|
+
item = {
|
380
|
+
"subscription": self.__get_subscription_string(),
|
381
|
+
"groups": [self.xmpp.ROSTER_GROUP],
|
382
|
+
}
|
383
|
+
if (n := self.name) is not None:
|
384
|
+
item["name"] = n
|
385
|
+
return {self.jid.bare: item}
|
386
|
+
|
316
387
|
async def add_to_roster(self, force=False):
|
317
388
|
"""
|
318
389
|
Add this contact to the user roster using :xep:`0356`
|
@@ -324,18 +395,10 @@ class LegacyContact(
|
|
324
395
|
if config.NO_ROSTER_PUSH:
|
325
396
|
log.debug("Roster push request by plugin ignored (--no-roster-push)")
|
326
397
|
return
|
327
|
-
item = {
|
328
|
-
"subscription": self.__get_subscription_string(),
|
329
|
-
"groups": [self.xmpp.ROSTER_GROUP],
|
330
|
-
}
|
331
|
-
if (n := self.name) is not None:
|
332
|
-
item["name"] = n
|
333
|
-
kw = dict(
|
334
|
-
jid=self.user_jid,
|
335
|
-
roster_items={self.jid.bare: item},
|
336
|
-
)
|
337
398
|
try:
|
338
|
-
await self._set_roster(
|
399
|
+
await self._set_roster(
|
400
|
+
jid=self.user_jid, roster_items=self.get_roster_item()
|
401
|
+
)
|
339
402
|
except PermissionError:
|
340
403
|
warnings.warn(
|
341
404
|
"Slidge does not have privileges to add contacts to the roster. Refer"
|
@@ -354,31 +417,32 @@ class LegacyContact(
|
|
354
417
|
# we only broadcast pubsub events for contacts added to the roster
|
355
418
|
# so if something was set before, we need to push it now
|
356
419
|
self.added_to_roster = True
|
357
|
-
self.session.create_task(self.__broadcast_pubsub_items())
|
358
420
|
self.send_last_presence()
|
359
421
|
|
360
422
|
async def __broadcast_pubsub_items(self):
|
423
|
+
if not self.is_friend:
|
424
|
+
return
|
425
|
+
if not self.added_to_roster:
|
426
|
+
return
|
361
427
|
cached_avatar = self.get_cached_avatar()
|
362
428
|
if cached_avatar is not None:
|
363
429
|
await self.xmpp.pubsub.broadcast_avatar(
|
364
430
|
self.jid.bare, self.session.user_jid, cached_avatar
|
365
431
|
)
|
366
432
|
nick = self.name
|
367
|
-
from ..core.pubsub import PepNick
|
368
433
|
|
369
434
|
if nick is not None:
|
370
|
-
|
371
|
-
await self.xmpp.pubsub.broadcast(
|
372
|
-
pep_nick.nick,
|
373
|
-
self.jid.bare,
|
435
|
+
self.xmpp.pubsub.broadcast_nick(
|
374
436
|
self.session.user_jid,
|
437
|
+
self.jid.bare,
|
438
|
+
nick,
|
375
439
|
)
|
376
440
|
|
377
441
|
async def _set_roster(self, **kw):
|
378
442
|
try:
|
379
|
-
|
443
|
+
await self.xmpp["xep_0356"].set_roster(**kw)
|
380
444
|
except PermissionError:
|
381
|
-
|
445
|
+
await self.xmpp["xep_0356_old"].set_roster(**kw)
|
382
446
|
|
383
447
|
def send_friend_request(self, text: Optional[str] = None):
|
384
448
|
presence = self._make_presence(ptype="subscribe", pstatus=text, bare=True)
|
@@ -392,8 +456,8 @@ class LegacyContact(
|
|
392
456
|
:param text: Optional message from the friend to the user
|
393
457
|
"""
|
394
458
|
self.is_friend = True
|
395
|
-
|
396
|
-
self.
|
459
|
+
self.added_to_roster = True
|
460
|
+
self.__ensure_pk()
|
397
461
|
self.log.debug("Accepting friend request")
|
398
462
|
presence = self._make_presence(ptype="subscribed", pstatus=text, bare=True)
|
399
463
|
self._send(presence, nick=True)
|
@@ -486,6 +550,22 @@ class LegacyContact(
|
|
486
550
|
"""
|
487
551
|
pass
|
488
552
|
|
553
|
+
def _make_presence(
|
554
|
+
self,
|
555
|
+
*,
|
556
|
+
last_seen: Optional[datetime.datetime] = None,
|
557
|
+
status_codes: Optional[set[int]] = None,
|
558
|
+
user_full_jid: Optional[JID] = None,
|
559
|
+
**presence_kwargs,
|
560
|
+
):
|
561
|
+
p = super()._make_presence(last_seen=last_seen, **presence_kwargs)
|
562
|
+
caps = self.xmpp.plugin["xep_0115"]
|
563
|
+
if p.get_from().resource and self._caps_ver:
|
564
|
+
p["caps"]["node"] = caps.caps_node
|
565
|
+
p["caps"]["hash"] = caps.hash
|
566
|
+
p["caps"]["ver"] = self._caps_ver
|
567
|
+
return p
|
568
|
+
|
489
569
|
@classmethod
|
490
570
|
def from_store(cls, session, stored: Contact, *args, **kwargs) -> Self:
|
491
571
|
contact = cls(
|
@@ -501,6 +581,8 @@ class LegacyContact(
|
|
501
581
|
contact.added_to_roster = stored.added_to_roster
|
502
582
|
if (data := stored.extra_attributes) is not None:
|
503
583
|
contact.deserialize_extra_attributes(data)
|
584
|
+
contact._caps_ver = stored.caps_ver
|
585
|
+
contact._set_logger_name()
|
504
586
|
contact._set_avatar_from_store(stored)
|
505
587
|
return contact
|
506
588
|
|
@@ -1,8 +1,10 @@
|
|
1
1
|
import asyncio
|
2
2
|
import logging
|
3
|
-
|
3
|
+
import warnings
|
4
|
+
from typing import TYPE_CHECKING, AsyncIterator, Generic, Iterator, Optional, Type
|
4
5
|
|
5
6
|
from slixmpp import JID
|
7
|
+
from slixmpp.exceptions import IqError, XMPPError
|
6
8
|
from slixmpp.jid import JID_UNESCAPE_TRANSFORMATIONS, _unescape_node
|
7
9
|
|
8
10
|
from ..core.mixins.lock import NamedLockMixin
|
@@ -50,6 +52,7 @@ class LegacyRoster(
|
|
50
52
|
self.log = logging.getLogger(f"{self.session.user_jid.bare}:roster")
|
51
53
|
self.user_legacy_id: Optional[LegacyUserIdType] = None
|
52
54
|
self.ready: asyncio.Future[bool] = self.session.xmpp.loop.create_future()
|
55
|
+
self.__filling = False
|
53
56
|
super().__init__()
|
54
57
|
|
55
58
|
def __repr__(self):
|
@@ -63,25 +66,37 @@ class LegacyRoster(
|
|
63
66
|
async def __finish_init_contact(
|
64
67
|
self, legacy_id: LegacyUserIdType, jid_username: str, *args, **kwargs
|
65
68
|
):
|
66
|
-
|
67
|
-
async with self.lock(("finish", c.legacy_id)):
|
69
|
+
async with self.lock(("finish", legacy_id)):
|
68
70
|
with self.__store.session():
|
69
71
|
stored = self.__store.get_by_legacy_id(
|
70
72
|
self.session.user_pk, str(legacy_id)
|
71
73
|
)
|
72
|
-
if stored is not None
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
74
|
+
if stored is not None:
|
75
|
+
if stored.updated:
|
76
|
+
return self._contact_cls.from_store(self.session, stored)
|
77
|
+
c: LegacyContact = self._contact_cls(
|
78
|
+
self.session, legacy_id, jid_username, *args, **kwargs
|
79
|
+
)
|
80
|
+
c.contact_pk = stored.id
|
81
|
+
else:
|
82
|
+
c = self._contact_cls(
|
83
|
+
self.session, legacy_id, jid_username, *args, **kwargs
|
84
|
+
)
|
85
|
+
try:
|
86
|
+
with c.updating_info():
|
87
|
+
await c.avatar_wrap_update_info()
|
88
|
+
except Exception as e:
|
89
|
+
raise XMPPError("internal-server-error", str(e))
|
90
|
+
c._caps_ver = await c.get_caps_ver(c.jid)
|
91
|
+
need_avatar = c.contact_pk is None
|
92
|
+
c.contact_pk = self.__store.update(c, commit=not self.__filling)
|
93
|
+
if need_avatar:
|
94
|
+
c._post_avatar_update()
|
80
95
|
return c
|
81
96
|
|
82
97
|
def known_contacts(self, only_friends=True) -> dict[str, LegacyContactType]:
|
83
98
|
if only_friends:
|
84
|
-
return {
|
99
|
+
return {c.jid.bare: c for c in self if c.is_friend}
|
85
100
|
return {c.jid.bare: c for c in self}
|
86
101
|
|
87
102
|
async def by_jid(self, contact_jid: JID) -> LegacyContactType:
|
@@ -189,18 +204,63 @@ class LegacyRoster(
|
|
189
204
|
"""
|
190
205
|
return _unescape_node(jid_username)
|
191
206
|
|
192
|
-
async def
|
207
|
+
async def _fill(self):
|
208
|
+
try:
|
209
|
+
if hasattr(self.session.xmpp, "TEST_MODE"):
|
210
|
+
# dirty hack to avoid mocking xmpp server replies to this
|
211
|
+
# during tests
|
212
|
+
raise PermissionError
|
213
|
+
iq = await self.session.xmpp["xep_0356"].get_roster(
|
214
|
+
self.session.user_jid.bare
|
215
|
+
)
|
216
|
+
user_roster = iq["roster"]["items"]
|
217
|
+
except (PermissionError, IqError):
|
218
|
+
user_roster = None
|
219
|
+
|
220
|
+
with self.__store.session() as orm:
|
221
|
+
self.__filling = True
|
222
|
+
async for contact in self.fill():
|
223
|
+
if user_roster is None:
|
224
|
+
continue
|
225
|
+
item = contact.get_roster_item()
|
226
|
+
old = user_roster.get(contact.jid.bare)
|
227
|
+
if old is not None and all(
|
228
|
+
old[k] == item[contact.jid.bare][k]
|
229
|
+
for k in ("subscription", "groups", "name")
|
230
|
+
):
|
231
|
+
self.log.debug("No need to update roster")
|
232
|
+
continue
|
233
|
+
self.log.debug("Updating roster")
|
234
|
+
try:
|
235
|
+
await self.session.xmpp["xep_0356"].set_roster(
|
236
|
+
self.session.user_jid.bare,
|
237
|
+
item,
|
238
|
+
)
|
239
|
+
except (PermissionError, IqError) as e:
|
240
|
+
warnings.warn(f"Could not add to roster: {e}")
|
241
|
+
else:
|
242
|
+
contact._added_to_roster = True
|
243
|
+
orm.commit()
|
244
|
+
self.__filling = False
|
245
|
+
|
246
|
+
async def fill(self) -> AsyncIterator[LegacyContact]:
|
193
247
|
"""
|
194
248
|
Populate slidge's "virtual roster".
|
195
249
|
|
196
|
-
|
197
|
-
|
198
|
-
|
250
|
+
This should yield contacts that are meant to be added to the user's
|
251
|
+
roster, typically by using ``await self.by_legacy_id(contact_id)``.
|
252
|
+
Setting the contact nicknames, avatar, etc. should be in
|
253
|
+
:meth:`LegacyContact.update_info()`
|
254
|
+
|
255
|
+
It's not mandatory to override this method, but it is recommended way
|
256
|
+
to populate "friends" of the user. Calling
|
257
|
+
``await (await self.by_legacy_id(contact_id)).add_to_roster()``
|
258
|
+
accomplishes the same thing, but doing it in here allows to batch
|
259
|
+
DB queries and is better performance-wise.
|
199
260
|
|
200
|
-
Await ``Contact.add_to_roster()`` in here to add the contact to the
|
201
|
-
user's XMPP roster.
|
202
261
|
"""
|
203
|
-
|
262
|
+
return
|
263
|
+
yield
|
204
264
|
|
205
265
|
|
206
266
|
ESCAPE_TABLE = "".maketrans({v: k for k, v in JID_UNESCAPE_TRANSFORMATIONS.items()})
|
@@ -185,10 +185,7 @@ LOG_FORMAT__DOC = (
|
|
185
185
|
)
|
186
186
|
|
187
187
|
MAM_MAX_DAYS = 7
|
188
|
-
MAM_MAX_DAYS__DOC =
|
189
|
-
"Maximum number of days for group archive retention. "
|
190
|
-
"Since all text content stored in RAM right now, "
|
191
|
-
)
|
188
|
+
MAM_MAX_DAYS__DOC = "Maximum number of days for group archive retention."
|
192
189
|
|
193
190
|
CORRECTION_EMPTY_BODY_AS_RETRACTION = True
|
194
191
|
CORRECTION_EMPTY_BODY_AS_RETRACTION__DOC = (
|
@@ -39,6 +39,7 @@ from ...slixfix.roster import RosterBackend
|
|
39
39
|
from ...slixfix.xep_0292.vcard4 import VCard4Provider
|
40
40
|
from ...util import ABCSubclassableOnceAtMost
|
41
41
|
from ...util.types import AvatarType, MessageOrPresenceTypeVar
|
42
|
+
from ...util.util import timeit
|
42
43
|
from .. import config
|
43
44
|
from ..mixins import MessageMixin
|
44
45
|
from ..pubsub import PubSubComponent
|
@@ -271,6 +272,7 @@ class BaseGateway(
|
|
271
272
|
"""
|
272
273
|
|
273
274
|
def __init__(self):
|
275
|
+
self.log = log
|
274
276
|
self.datetime_started = datetime.now()
|
275
277
|
self.xmpp = self # ugly hack to work with the BaseSender mixin :/
|
276
278
|
self.default_ns = "jabber:component:accept"
|
@@ -335,6 +337,8 @@ class BaseGateway(
|
|
335
337
|
# with this we receive user avatar updates
|
336
338
|
self.plugin["xep_0030"].add_feature("urn:xmpp:avatar:metadata+notify")
|
337
339
|
|
340
|
+
self.plugin["xep_0030"].add_feature("urn:xmpp:chat-markers:0")
|
341
|
+
|
338
342
|
if self.GROUPS:
|
339
343
|
self.plugin["xep_0030"].add_feature("http://jabber.org/protocol/muc")
|
340
344
|
self.plugin["xep_0030"].add_feature("urn:xmpp:mam:2")
|
@@ -366,6 +370,8 @@ class BaseGateway(
|
|
366
370
|
|
367
371
|
self.__mam_cleanup_task = self.loop.create_task(self.__mam_cleanup())
|
368
372
|
|
373
|
+
MessageMixin.__init__(self) # ComponentXMPP does not call super().__init__()
|
374
|
+
|
369
375
|
async def __mam_cleanup(self):
|
370
376
|
if not config.MAM_MAX_DAYS:
|
371
377
|
return
|
@@ -399,7 +405,7 @@ class BaseGateway(
|
|
399
405
|
log.debug("Context in the exception handler: %s", context)
|
400
406
|
exc = context.get("exception")
|
401
407
|
if exc is None:
|
402
|
-
log.
|
408
|
+
log.debug("No exception in this context: %s", context)
|
403
409
|
elif isinstance(exc, SystemExit):
|
404
410
|
log.debug("SystemExit called in an asyncio task")
|
405
411
|
else:
|
@@ -540,6 +546,7 @@ class BaseGateway(
|
|
540
546
|
exc_info=e,
|
541
547
|
)
|
542
548
|
|
549
|
+
@timeit
|
543
550
|
async def __login_wrap(self, session: "BaseSession"):
|
544
551
|
session.send_gateway_status("Logging in…", show="dnd")
|
545
552
|
try:
|
@@ -557,7 +564,7 @@ class BaseGateway(
|
|
557
564
|
log.info("Login success for %s", session.user_jid)
|
558
565
|
session.logged = True
|
559
566
|
session.send_gateway_status("Syncing contacts…", show="dnd")
|
560
|
-
await session.contacts.
|
567
|
+
await session.contacts._fill()
|
561
568
|
if not (r := session.contacts.ready).done():
|
562
569
|
r.set_result(True)
|
563
570
|
if self.GROUPS:
|
@@ -5,8 +5,6 @@ from slixmpp import Presence
|
|
5
5
|
from slixmpp.exceptions import XMPPError
|
6
6
|
from slixmpp.xmlstream import StanzaBase
|
7
7
|
|
8
|
-
from ...contact import LegacyContact
|
9
|
-
|
10
8
|
if TYPE_CHECKING:
|
11
9
|
from .base import BaseGateway
|
12
10
|
|
@@ -25,6 +23,9 @@ class Caps:
|
|
25
23
|
if not isinstance(stanza, Presence):
|
26
24
|
return stanza
|
27
25
|
|
26
|
+
if stanza.get_plugin("caps", check=True):
|
27
|
+
return stanza
|
28
|
+
|
28
29
|
if stanza["type"] not in ("available", "chat", "away", "dnd", "xa"):
|
29
30
|
return stanza
|
30
31
|
|
@@ -44,10 +45,11 @@ class Caps:
|
|
44
45
|
|
45
46
|
await session.ready
|
46
47
|
|
47
|
-
|
48
|
-
|
48
|
+
try:
|
49
|
+
contact = await session.contacts.by_jid(pfrom)
|
50
|
+
except XMPPError:
|
49
51
|
return stanza
|
50
|
-
ver = await
|
52
|
+
ver = await contact.get_caps_ver(pfrom)
|
51
53
|
else:
|
52
54
|
ver = await caps.get_verstring(pfrom)
|
53
55
|
|
@@ -28,7 +28,7 @@ class MucAdmin:
|
|
28
28
|
|
29
29
|
reply = iq.reply()
|
30
30
|
reply.enable("mucadmin_query")
|
31
|
-
for participant in
|
31
|
+
async for participant in muc.get_participants():
|
32
32
|
if not participant.affiliation == affiliation:
|
33
33
|
continue
|
34
34
|
reply["mucadmin_query"].append(participant.mucadmin_item())
|
@@ -632,14 +632,28 @@ class SessionDispatcher:
|
|
632
632
|
session = await self.__get_session(iq)
|
633
633
|
session.raise_if_not_logged()
|
634
634
|
|
635
|
-
item = iq["mucadmin_query"]["item"]
|
636
|
-
contact = await session.contacts.by_jid(JID(item["jid"]))
|
637
|
-
|
638
635
|
muc = await session.bookmarks.by_jid(iq.get_to())
|
639
636
|
|
640
|
-
|
641
|
-
|
642
|
-
|
637
|
+
item = iq["mucadmin_query"]["item"]
|
638
|
+
if item["jid"]:
|
639
|
+
contact = await session.contacts.by_jid(JID(item["jid"]))
|
640
|
+
else:
|
641
|
+
part = await muc.get_participant(
|
642
|
+
item["nick"], fill_first=True, raise_if_not_found=True
|
643
|
+
)
|
644
|
+
assert part.contact is not None
|
645
|
+
contact = part.contact
|
646
|
+
|
647
|
+
if item["affiliation"]:
|
648
|
+
await muc.on_set_affiliation(
|
649
|
+
contact,
|
650
|
+
item["affiliation"],
|
651
|
+
item["reason"] or None,
|
652
|
+
item["nick"] or None,
|
653
|
+
)
|
654
|
+
elif item["role"] == "none":
|
655
|
+
await muc.on_kick(contact, item["reason"] or None)
|
656
|
+
|
643
657
|
iq.reply(clear=True).send()
|
644
658
|
|
645
659
|
async def on_groupchat_direct_invite(self, msg: Message):
|
@@ -61,7 +61,7 @@ class VCardTemp:
|
|
61
61
|
|
62
62
|
async def __handle_get_vcard_temp(self, iq: Iq):
|
63
63
|
session = self.xmpp.get_session_from_stanza(iq)
|
64
|
-
entity = await session.get_contact_or_group_or_participant(iq.get_to())
|
64
|
+
entity = await session.get_contact_or_group_or_participant(iq.get_to(), False)
|
65
65
|
if not entity:
|
66
66
|
raise XMPPError("item-not-found")
|
67
67
|
|