slidge 0.3.0b3__py3-none-any.whl → 0.3.1__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 +0 -1
- slidge/command/user.py +25 -6
- slidge/contact/contact.py +3 -0
- slidge/core/dispatcher/muc/misc.py +27 -2
- slidge/core/dispatcher/presence.py +31 -26
- slidge/core/gateway.py +7 -0
- slidge/core/mixins/base.py +2 -0
- slidge/core/mixins/db.py +23 -1
- slidge/core/mixins/message_maker.py +2 -2
- slidge/core/mixins/message_text.py +29 -0
- slidge/core/mixins/presence.py +5 -4
- slidge/core/pubsub.py +1 -1
- slidge/core/session.py +11 -0
- slidge/group/archive.py +7 -1
- slidge/group/bookmarks.py +1 -1
- slidge/group/participant.py +34 -2
- slidge/group/room.py +20 -4
- slidge/migration.py +0 -11
- slidge/slixfix/xep_0292/vcard4.py +11 -1
- slidge/util/util.py +7 -0
- {slidge-0.3.0b3.dist-info → slidge-0.3.1.dist-info}/METADATA +1 -1
- {slidge-0.3.0b3.dist-info → slidge-0.3.1.dist-info}/RECORD +26 -26
- {slidge-0.3.0b3.dist-info → slidge-0.3.1.dist-info}/WHEEL +0 -0
- {slidge-0.3.0b3.dist-info → slidge-0.3.1.dist-info}/entry_points.txt +0 -0
- {slidge-0.3.0b3.dist-info → slidge-0.3.1.dist-info}/licenses/LICENSE +0 -0
- {slidge-0.3.0b3.dist-info → slidge-0.3.1.dist-info}/top_level.txt +0 -0
slidge/__init__.py
CHANGED
slidge/command/user.py
CHANGED
@@ -34,8 +34,9 @@ class Search(Command):
|
|
34
34
|
async def run(
|
35
35
|
self, session: Optional[AnyBaseSession], _ifrom: JID, *args: str
|
36
36
|
) -> Union[Form, SearchResult, None]:
|
37
|
+
assert session is not None
|
38
|
+
await session.ready
|
37
39
|
if args:
|
38
|
-
assert session is not None
|
39
40
|
return await session.on_search(
|
40
41
|
{self.xmpp.SEARCH_FIELDS[0].var: " ".join(args)}
|
41
42
|
)
|
@@ -70,6 +71,8 @@ class SyncContacts(Command):
|
|
70
71
|
CATEGORY = CONTACTS
|
71
72
|
|
72
73
|
async def run(self, session: Optional[AnyBaseSession], _ifrom, *_) -> Confirmation:
|
74
|
+
assert session is not None
|
75
|
+
await session.ready
|
73
76
|
return Confirmation(
|
74
77
|
prompt="Are you sure you want to sync your roster?",
|
75
78
|
success=None,
|
@@ -133,6 +136,7 @@ class ListContacts(Command):
|
|
133
136
|
self, session: Optional[AnyBaseSession], _ifrom: JID, *_
|
134
137
|
) -> TableResult:
|
135
138
|
assert session is not None
|
139
|
+
await session.ready
|
136
140
|
contacts = sorted(
|
137
141
|
session.contacts, key=lambda c: c.name.casefold() if c.name else ""
|
138
142
|
)
|
@@ -152,7 +156,7 @@ class ListGroups(Command):
|
|
152
156
|
|
153
157
|
async def run(self, session, _ifrom, *_):
|
154
158
|
assert session is not None
|
155
|
-
await session.
|
159
|
+
await session.ready
|
156
160
|
groups = sorted(session.bookmarks, key=lambda g: g.DISCO_NAME.casefold())
|
157
161
|
return TableResult(
|
158
162
|
description="Your groups",
|
@@ -201,6 +205,7 @@ class CreateGroup(Command):
|
|
201
205
|
|
202
206
|
async def run(self, session: Optional[AnyBaseSession], _ifrom, *_):
|
203
207
|
assert session is not None
|
208
|
+
await session.ready
|
204
209
|
contacts = session.contacts.known_contacts(only_friends=True)
|
205
210
|
return Form(
|
206
211
|
title="Create a new group",
|
@@ -260,20 +265,34 @@ class Preferences(Command):
|
|
260
265
|
instructions=self.HELP,
|
261
266
|
fields=fields,
|
262
267
|
handler=self.finish, # type:ignore
|
268
|
+
handler_kwargs={"previous": current},
|
263
269
|
)
|
264
270
|
|
265
271
|
async def finish(
|
266
|
-
self,
|
272
|
+
self,
|
273
|
+
form_values: UserPreferences,
|
274
|
+
session: Optional[AnyBaseSession],
|
275
|
+
*_,
|
276
|
+
previous,
|
267
277
|
) -> str:
|
268
278
|
assert session is not None
|
279
|
+
if previous == form_values:
|
280
|
+
return "No preference was changed"
|
281
|
+
|
269
282
|
user = session.user
|
270
283
|
user.preferences.update(form_values) # type:ignore
|
271
|
-
|
284
|
+
self.xmpp.store.users.update(user)
|
285
|
+
|
286
|
+
try:
|
287
|
+
await session.on_preferences(previous, form_values) # type:ignore[arg-type]
|
288
|
+
except NotImplementedError:
|
289
|
+
pass
|
290
|
+
|
291
|
+
if not previous["sync_avatar"] and form_values["sync_avatar"]:
|
272
292
|
await self.xmpp.fetch_user_avatar(session)
|
273
293
|
else:
|
274
294
|
user.avatar_hash = None
|
275
295
|
|
276
|
-
self.xmpp.store.users.update(user)
|
277
296
|
return "Your preferences have been updated."
|
278
297
|
|
279
298
|
|
@@ -308,7 +327,7 @@ class LeaveGroup(Command):
|
|
308
327
|
|
309
328
|
async def run(self, session, _ifrom, *_):
|
310
329
|
assert session is not None
|
311
|
-
await session.
|
330
|
+
await session.ready
|
312
331
|
groups = sorted(session.bookmarks, key=lambda g: g.DISCO_NAME.casefold())
|
313
332
|
return Form(
|
314
333
|
title="Leave a group",
|
slidge/contact/contact.py
CHANGED
@@ -325,6 +325,7 @@ class LegacyContact(
|
|
325
325
|
email: Optional[str] = None,
|
326
326
|
country: Optional[str] = None,
|
327
327
|
locality: Optional[str] = None,
|
328
|
+
pronouns: Optional[str] = None,
|
328
329
|
) -> None:
|
329
330
|
vcard = VCard4()
|
330
331
|
vcard.add_impp(f"xmpp:{self.jid.bare}")
|
@@ -357,6 +358,8 @@ class LegacyContact(
|
|
357
358
|
vcard.add_address(country, locality)
|
358
359
|
elif country:
|
359
360
|
vcard.add_address(country, locality)
|
361
|
+
if pronouns:
|
362
|
+
vcard["pronouns"]["text"] = pronouns
|
360
363
|
|
361
364
|
self.stored.vcard = str(vcard)
|
362
365
|
self.stored.vcard_fetched = True
|
@@ -1,6 +1,14 @@
|
|
1
1
|
import logging
|
2
2
|
|
3
|
-
from slixmpp import
|
3
|
+
from slixmpp import (
|
4
|
+
JID,
|
5
|
+
CoroutineCallback,
|
6
|
+
Iq,
|
7
|
+
MatchXMLMask,
|
8
|
+
Message,
|
9
|
+
Presence,
|
10
|
+
StanzaPath,
|
11
|
+
)
|
4
12
|
from slixmpp.exceptions import XMPPError
|
5
13
|
|
6
14
|
from ..util import DispatcherMixin, exceptions_to_xmpp_errors
|
@@ -23,6 +31,15 @@ class MucMiscMixin(DispatcherMixin):
|
|
23
31
|
)
|
24
32
|
xmpp.add_event_handler("groupchat_subject", self.on_groupchat_subject)
|
25
33
|
xmpp.add_event_handler("groupchat_message_error", self.__on_group_chat_error)
|
34
|
+
xmpp.register_handler(
|
35
|
+
CoroutineCallback(
|
36
|
+
"muc_thread_subject",
|
37
|
+
MatchXMLMask(
|
38
|
+
"<message xmlns='jabber:component:accept' type='groupchat'><subject/><thread/></message>"
|
39
|
+
),
|
40
|
+
self.on_thread_subject,
|
41
|
+
)
|
42
|
+
)
|
26
43
|
|
27
44
|
async def __on_group_chat_error(self, msg: Message) -> None:
|
28
45
|
condition = msg["error"].get_condition()
|
@@ -43,7 +60,7 @@ class MucMiscMixin(DispatcherMixin):
|
|
43
60
|
# (not sure why?), but is of no consequence
|
44
61
|
log.debug("%s was not in the resources of %s", resource, muc)
|
45
62
|
else:
|
46
|
-
log.
|
63
|
+
log.debug(
|
47
64
|
"Removed %s from the resources of %s because of error", resource, muc
|
48
65
|
)
|
49
66
|
|
@@ -106,6 +123,14 @@ class MucMiscMixin(DispatcherMixin):
|
|
106
123
|
)
|
107
124
|
await muc.on_set_subject(msg["subject"])
|
108
125
|
|
126
|
+
@exceptions_to_xmpp_errors
|
127
|
+
async def on_thread_subject(self, msg: Message):
|
128
|
+
if msg["body"]:
|
129
|
+
return
|
130
|
+
session, muc, thread = await self._get_session_recipient_thread(msg)
|
131
|
+
assert thread is not None
|
132
|
+
await muc.on_set_thread_subject(thread, msg["subject"]) # type:ignore[union-attr]
|
133
|
+
|
109
134
|
|
110
135
|
KICKABLE_ERRORS = {
|
111
136
|
"gone",
|
@@ -4,6 +4,7 @@ from slixmpp import JID, Presence
|
|
4
4
|
from slixmpp.exceptions import XMPPError
|
5
5
|
|
6
6
|
from ...contact.roster import ContactIsUser
|
7
|
+
from ...util.types import AnyBaseSession
|
7
8
|
from ...util.util import merge_resources
|
8
9
|
from ..session import BaseSession
|
9
10
|
from .util import DispatcherMixin, exceptions_to_xmpp_errors
|
@@ -98,7 +99,7 @@ class PresenceHandlerMixin(DispatcherMixin):
|
|
98
99
|
reply.send()
|
99
100
|
|
100
101
|
@exceptions_to_xmpp_errors
|
101
|
-
async def on_presence(self, p: Presence):
|
102
|
+
async def on_presence(self, p: Presence) -> None:
|
102
103
|
if p.get_plugin("muc_join", check=True):
|
103
104
|
# handled in on_groupchat_join
|
104
105
|
# without this early return, since we switch from and to in this
|
@@ -112,31 +113,8 @@ class PresenceHandlerMixin(DispatcherMixin):
|
|
112
113
|
|
113
114
|
pto = p.get_to()
|
114
115
|
if pto == self.xmpp.boundjid.bare:
|
115
|
-
|
116
|
-
|
117
|
-
return
|
118
|
-
if not session.user.preferences.get("sync_presence", False):
|
119
|
-
session.log.debug("User does not want to sync their presence")
|
120
|
-
return
|
121
|
-
# NB: get_type() returns either a proper presence type or
|
122
|
-
# a presence show if available. Weird, weird, weird slix.
|
123
|
-
resources = self.xmpp.roster[self.xmpp.boundjid.bare][
|
124
|
-
p.get_from()
|
125
|
-
].resources
|
126
|
-
try:
|
127
|
-
await session.on_presence(
|
128
|
-
p.get_from().resource,
|
129
|
-
ptype, # type: ignore
|
130
|
-
p["status"],
|
131
|
-
resources,
|
132
|
-
merge_resources(resources),
|
133
|
-
)
|
134
|
-
except NotImplementedError:
|
135
|
-
pass
|
136
|
-
if p.get_type() == "available":
|
137
|
-
await self.xmpp.pubsub.on_presence_available(p, None)
|
138
|
-
for contact in session.contacts:
|
139
|
-
await self.xmpp.pubsub.on_presence_available(p, contact)
|
116
|
+
await self._on_presence_to_component(session, p)
|
117
|
+
return
|
140
118
|
|
141
119
|
if p.get_type() == "available":
|
142
120
|
try:
|
@@ -181,6 +159,33 @@ class PresenceHandlerMixin(DispatcherMixin):
|
|
181
159
|
)
|
182
160
|
error_stanza.send()
|
183
161
|
|
162
|
+
async def _on_presence_to_component(
|
163
|
+
self, session: AnyBaseSession, p: Presence
|
164
|
+
) -> None:
|
165
|
+
session.log.debug("Received a presence from %s", p.get_from())
|
166
|
+
if (ptype := p.get_type()) not in _USEFUL_PRESENCES:
|
167
|
+
return
|
168
|
+
if not session.user.preferences.get("sync_presence", False):
|
169
|
+
session.log.debug("User does not want to sync their presence")
|
170
|
+
return
|
171
|
+
# NB: get_type() returns either a proper presence type or
|
172
|
+
# a presence show if available. Weird, weird, weird slix.
|
173
|
+
resources = self.xmpp.roster[self.xmpp.boundjid.bare][p.get_from()].resources
|
174
|
+
try:
|
175
|
+
await session.on_presence(
|
176
|
+
p.get_from().resource,
|
177
|
+
ptype, # type: ignore
|
178
|
+
p["status"],
|
179
|
+
resources,
|
180
|
+
merge_resources(resources),
|
181
|
+
)
|
182
|
+
except NotImplementedError:
|
183
|
+
pass
|
184
|
+
if p.get_type() == "available":
|
185
|
+
await self.xmpp.pubsub.on_presence_available(p, None)
|
186
|
+
for contact in session.contacts:
|
187
|
+
await self.xmpp.pubsub.on_presence_available(p, contact)
|
188
|
+
|
184
189
|
|
185
190
|
_USEFUL_PRESENCES = {"available", "unavailable", "away", "chat", "dnd", "xa"}
|
186
191
|
|
slidge/core/gateway.py
CHANGED
@@ -172,6 +172,13 @@ class BaseGateway(
|
|
172
172
|
required=True,
|
173
173
|
type="boolean",
|
174
174
|
),
|
175
|
+
FormField(
|
176
|
+
var="reaction_fallback",
|
177
|
+
label="Receive fallback messages for reactions (for legacy XMPP clients)",
|
178
|
+
value="false",
|
179
|
+
required=True,
|
180
|
+
type="boolean",
|
181
|
+
),
|
175
182
|
]
|
176
183
|
|
177
184
|
ROSTER_GROUP: str = "slidge"
|
slidge/core/mixins/base.py
CHANGED
slidge/core/mixins/db.py
CHANGED
@@ -2,7 +2,7 @@ import logging
|
|
2
2
|
import typing
|
3
3
|
from contextlib import contextmanager
|
4
4
|
|
5
|
-
from ...db.models import Base, Contact,
|
5
|
+
from ...db.models import Base, Contact, Room
|
6
6
|
|
7
7
|
if typing.TYPE_CHECKING:
|
8
8
|
from slidge import BaseGateway
|
@@ -41,13 +41,35 @@ class UpdateInfoMixin(DBMixin):
|
|
41
41
|
def __init__(self, *args, **kwargs) -> None:
|
42
42
|
super().__init__(*args, **kwargs)
|
43
43
|
self._updating_info = False
|
44
|
+
self.__deserialize()
|
45
|
+
|
46
|
+
def __deserialize(self):
|
44
47
|
if self.stored.extra_attributes is not None:
|
45
48
|
self.deserialize_extra_attributes(self.stored.extra_attributes)
|
46
49
|
|
50
|
+
def refresh(self) -> None:
|
51
|
+
with self.xmpp.store.session(expire_on_commit=False) as orm:
|
52
|
+
orm.add(self.stored)
|
53
|
+
orm.refresh(self.stored)
|
54
|
+
self.__deserialize()
|
55
|
+
|
47
56
|
def serialize_extra_attributes(self) -> dict | None:
|
57
|
+
"""
|
58
|
+
If you want custom attributes of your instance to be stored persistently
|
59
|
+
to the DB, here is where you have to return them as a dict to be used in
|
60
|
+
`deserialize_extra_attributes()`.
|
61
|
+
|
62
|
+
"""
|
48
63
|
return None
|
49
64
|
|
50
65
|
def deserialize_extra_attributes(self, data: dict) -> None:
|
66
|
+
"""
|
67
|
+
This is where you get the dict that you passed in
|
68
|
+
`serialize_extra_attributes()`.
|
69
|
+
|
70
|
+
⚠ Since it is serialized as json, dictionary keys are converted to strings!
|
71
|
+
Be sure to convert to other types if necessary.
|
72
|
+
"""
|
51
73
|
pass
|
52
74
|
|
53
75
|
@contextmanager
|
@@ -1,7 +1,7 @@
|
|
1
|
+
import uuid
|
1
2
|
import warnings
|
2
3
|
from datetime import datetime, timezone
|
3
4
|
from typing import TYPE_CHECKING, Iterable, Optional, cast
|
4
|
-
from uuid import uuid4
|
5
5
|
|
6
6
|
from slixmpp import JID, Message
|
7
7
|
from slixmpp.types import MessageTypes
|
@@ -95,7 +95,7 @@ class MessageMaker(BaseSender):
|
|
95
95
|
msg["stanza_id"]["id"] = i
|
96
96
|
msg["stanza_id"]["by"] = self.muc.jid # type: ignore
|
97
97
|
elif self.USE_STANZA_ID:
|
98
|
-
msg["stanza_id"]["id"] = str(uuid4())
|
98
|
+
msg["stanza_id"]["id"] = str(uuid.uuid4())
|
99
99
|
msg["stanza_id"]["by"] = self.muc.jid # type: ignore
|
100
100
|
|
101
101
|
def _legacy_to_xmpp(self, legacy_id: LegacyMessageType) -> str:
|
@@ -2,6 +2,9 @@ import logging
|
|
2
2
|
from datetime import datetime
|
3
3
|
from typing import Iterable, Optional
|
4
4
|
|
5
|
+
from slixmpp import Message
|
6
|
+
|
7
|
+
from ...util.archive_msg import HistoryMessage
|
5
8
|
from ...util.types import (
|
6
9
|
LegacyMessageType,
|
7
10
|
LegacyThreadType,
|
@@ -9,6 +12,7 @@ from ...util.types import (
|
|
9
12
|
MessageReference,
|
10
13
|
ProcessingHint,
|
11
14
|
)
|
15
|
+
from ...util.util import add_quote_prefix
|
12
16
|
from .message_maker import MessageMaker
|
13
17
|
|
14
18
|
|
@@ -188,8 +192,33 @@ class TextMessageMixin(MessageMaker):
|
|
188
192
|
if not xmpp_id:
|
189
193
|
xmpp_id = self._legacy_to_xmpp(legacy_msg_id)
|
190
194
|
self.xmpp["xep_0444"].set_reactions(msg, to_id=xmpp_id, reactions=emojis)
|
195
|
+
self.__add_reaction_fallback(msg, legacy_msg_id, emojis)
|
191
196
|
self._send(msg, **kwargs)
|
192
197
|
|
198
|
+
def __add_reaction_fallback(
|
199
|
+
self,
|
200
|
+
msg: Message,
|
201
|
+
legacy_msg_id: LegacyMessageType,
|
202
|
+
emojis: Iterable[str] = (),
|
203
|
+
) -> None:
|
204
|
+
if not self.session.user.preferences.get("reaction_fallback", False):
|
205
|
+
return
|
206
|
+
msg["fallback"]["for"] = self.xmpp.plugin["xep_0444"].namespace
|
207
|
+
msg["fallback"].enable("body")
|
208
|
+
msg["body"] = " ".join(emojis)
|
209
|
+
if not self.is_participant:
|
210
|
+
return
|
211
|
+
with self.xmpp.store.session() as orm:
|
212
|
+
archived = self.xmpp.store.mam.get_by_legacy_id(
|
213
|
+
orm, self.muc.stored.id, str(legacy_msg_id)
|
214
|
+
)
|
215
|
+
if archived is None:
|
216
|
+
return
|
217
|
+
history_msg = HistoryMessage(archived.stanza)
|
218
|
+
msg["body"] = (
|
219
|
+
add_quote_prefix(history_msg.stanza["body"]) + "\n" + msg["body"]
|
220
|
+
)
|
221
|
+
|
193
222
|
def retract(
|
194
223
|
self,
|
195
224
|
legacy_msg_id: LegacyMessageType,
|
slidge/core/mixins/presence.py
CHANGED
@@ -62,8 +62,9 @@ class PresenceMixin(BaseSender, DBMixin):
|
|
62
62
|
try:
|
63
63
|
return self.stored.contact
|
64
64
|
except DetachedInstanceError:
|
65
|
-
self.
|
66
|
-
|
65
|
+
with self.xmpp.store.session() as orm:
|
66
|
+
orm.add(self.stored)
|
67
|
+
return self.stored.contact
|
67
68
|
|
68
69
|
@property
|
69
70
|
def __contact_pk(self) -> int | None:
|
@@ -278,9 +279,9 @@ class PresenceMixin(BaseSender, DBMixin):
|
|
278
279
|
pass
|
279
280
|
|
280
281
|
|
281
|
-
def get_last_seen_fallback(last_seen: datetime):
|
282
|
+
def get_last_seen_fallback(last_seen: datetime) -> tuple[str, bool]:
|
282
283
|
now = datetime.now(tz=timezone.utc)
|
283
284
|
if now - last_seen < timedelta(days=7):
|
284
|
-
return f"Last seen {last_seen:%A %H:%M GMT}", True
|
285
|
+
return f"Last seen {last_seen:%A %H:%M %p GMT}", True
|
285
286
|
else:
|
286
287
|
return f"Last seen {last_seen:%b %-d %Y}", False
|
slidge/core/pubsub.py
CHANGED
@@ -120,7 +120,7 @@ class PubSubComponent(NamedLockMixin, BasePlugin):
|
|
120
120
|
try:
|
121
121
|
iq = await self.xmpp.plugin["xep_0030"].get_info(from_)
|
122
122
|
except (IqError, IqTimeout):
|
123
|
-
log.debug("Could get disco#info of %s, ignoring", from_)
|
123
|
+
log.debug("Could not get disco#info of %s, ignoring", from_)
|
124
124
|
return []
|
125
125
|
info = iq["disco_info"]
|
126
126
|
return info["features"]
|
slidge/core/session.py
CHANGED
@@ -539,6 +539,17 @@ class BaseSession(
|
|
539
539
|
"""
|
540
540
|
raise NotImplementedError
|
541
541
|
|
542
|
+
async def on_preferences(
|
543
|
+
self, previous: dict[str, Any], new: dict[str, Any]
|
544
|
+
) -> None:
|
545
|
+
"""
|
546
|
+
This is called when the user updates their preferences.
|
547
|
+
|
548
|
+
Override this if you need set custom preferences field and need to trigger
|
549
|
+
something when a preference has changed.
|
550
|
+
"""
|
551
|
+
raise NotImplementedError
|
552
|
+
|
542
553
|
def __reset_ready(self) -> None:
|
543
554
|
self.ready = self.xmpp.loop.create_future()
|
544
555
|
|
slidge/group/archive.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
import logging
|
2
2
|
import uuid
|
3
|
+
import warnings
|
3
4
|
from copy import copy
|
4
5
|
from datetime import datetime, timezone
|
5
6
|
from typing import TYPE_CHECKING, Collection, Optional
|
@@ -48,7 +49,9 @@ class MessageArchive:
|
|
48
49
|
elif participant.is_system:
|
49
50
|
new_msg["muc"]["jid"] = participant.muc.jid
|
50
51
|
else:
|
51
|
-
|
52
|
+
warnings.warn(
|
53
|
+
f"No real JID for participant '{participant.nickname}' in '{self.room.name}'"
|
54
|
+
)
|
52
55
|
new_msg["muc"]["jid"] = (
|
53
56
|
f"{uuid.uuid4()}@{participant.xmpp.boundjid.bare}"
|
54
57
|
)
|
@@ -178,6 +181,9 @@ def archivable(msg: Message) -> bool:
|
|
178
181
|
if msg.get_plugin("displayed", check=True):
|
179
182
|
return True
|
180
183
|
|
184
|
+
if msg["thread"] and msg["subject"]:
|
185
|
+
return True
|
186
|
+
|
181
187
|
return False
|
182
188
|
|
183
189
|
|
slidge/group/bookmarks.py
CHANGED
slidge/group/participant.py
CHANGED
@@ -6,8 +6,8 @@ from copy import copy
|
|
6
6
|
from datetime import datetime
|
7
7
|
from functools import cached_property
|
8
8
|
from typing import TYPE_CHECKING, Any, Optional, Union
|
9
|
+
from xml.etree import ElementTree as ET
|
9
10
|
|
10
|
-
import sqlalchemy as sa
|
11
11
|
from slixmpp import JID, InvalidJID, Message, Presence
|
12
12
|
from slixmpp.plugins.xep_0045.stanza import MUCAdminItem
|
13
13
|
from slixmpp.types import MessageTypes, OptJid
|
@@ -22,6 +22,7 @@ from ..util.types import (
|
|
22
22
|
CachedPresence,
|
23
23
|
Hat,
|
24
24
|
LegacyMessageType,
|
25
|
+
LegacyThreadType,
|
25
26
|
MessageOrPresenceTypeVar,
|
26
27
|
MucAffiliation,
|
27
28
|
MucRole,
|
@@ -51,6 +52,8 @@ class LegacyParticipant(
|
|
51
52
|
A legacy participant of a legacy group chat.
|
52
53
|
"""
|
53
54
|
|
55
|
+
is_participant = True
|
56
|
+
|
54
57
|
mtype: MessageTypes = "groupchat"
|
55
58
|
_can_send_carbon = False
|
56
59
|
USE_STANZA_ID = True
|
@@ -93,6 +96,13 @@ class LegacyParticipant(
|
|
93
96
|
self.merge()
|
94
97
|
return self.stored.is_user
|
95
98
|
|
99
|
+
@is_user.setter
|
100
|
+
def is_user(self, is_user: bool) -> None:
|
101
|
+
with self.xmpp.store.session(expire_on_commit=True) as orm:
|
102
|
+
orm.add(self.stored)
|
103
|
+
self.stored.is_user = is_user
|
104
|
+
orm.commit()
|
105
|
+
|
96
106
|
@property
|
97
107
|
def jid(self) -> JID:
|
98
108
|
jid = JID(self.muc.jid)
|
@@ -510,9 +520,31 @@ class LegacyParticipant(
|
|
510
520
|
if when is not None:
|
511
521
|
msg["delay"].set_stamp(when)
|
512
522
|
msg["delay"]["from"] = self.muc.jid
|
513
|
-
|
523
|
+
if subject:
|
524
|
+
msg["subject"] = subject
|
525
|
+
else:
|
526
|
+
# may be simplified if slixmpp lets it do it more easily some day
|
527
|
+
msg.xml.append(ET.Element(f"{{{msg.namespace}}}subject"))
|
514
528
|
self._send(msg, full_jid)
|
515
529
|
|
530
|
+
def set_thread_subject(
|
531
|
+
self,
|
532
|
+
thread: LegacyThreadType,
|
533
|
+
subject: str | None,
|
534
|
+
when: Optional[datetime] = None,
|
535
|
+
) -> None:
|
536
|
+
msg = self._make_message()
|
537
|
+
msg["thread"] = str(thread)
|
538
|
+
if when is not None:
|
539
|
+
msg["delay"].set_stamp(when)
|
540
|
+
msg["delay"]["from"] = self.muc.jid
|
541
|
+
if subject:
|
542
|
+
msg["subject"] = subject
|
543
|
+
else:
|
544
|
+
# may be simplified if slixmpp lets it do it more easily some day
|
545
|
+
msg.xml.append(ET.Element(f"{{{msg.namespace}}}subject"))
|
546
|
+
self._send(msg)
|
547
|
+
|
516
548
|
|
517
549
|
def escape_nickname(muc_jid: JID, nickname: str) -> tuple[str, JID]:
|
518
550
|
nickname = nickname_no_illegal = strip_illegal_chars(nickname)
|
slidge/group/room.py
CHANGED
@@ -43,6 +43,7 @@ from ..util.types import (
|
|
43
43
|
LegacyGroupIdType,
|
44
44
|
LegacyMessageType,
|
45
45
|
LegacyParticipantType,
|
46
|
+
LegacyThreadType,
|
46
47
|
LegacyUserIdType,
|
47
48
|
Mention,
|
48
49
|
MucAffiliation,
|
@@ -431,8 +432,8 @@ class LegacyMUC(
|
|
431
432
|
yield
|
432
433
|
|
433
434
|
@property
|
434
|
-
def subject(self):
|
435
|
-
return self.stored.subject
|
435
|
+
def subject(self) -> str:
|
436
|
+
return self.stored.subject or ""
|
436
437
|
|
437
438
|
@subject.setter
|
438
439
|
def subject(self, s: str) -> None:
|
@@ -652,7 +653,7 @@ class LegacyMUC(
|
|
652
653
|
orm.refresh(self.stored, ["participants"])
|
653
654
|
if not user_participant.is_user:
|
654
655
|
self.log.warning("is_user flag not set participant on user_participant")
|
655
|
-
user_participant.is_user = True
|
656
|
+
user_participant.is_user = True
|
656
657
|
user_participant.send_initial_presence(
|
657
658
|
user_full_jid,
|
658
659
|
presence_id=join_presence["id"],
|
@@ -680,8 +681,12 @@ class LegacyMUC(
|
|
680
681
|
maxstanzas=maxstanzas,
|
681
682
|
since=since,
|
682
683
|
)
|
684
|
+
if self.HAS_SUBJECT:
|
685
|
+
subject = self.subject or ""
|
686
|
+
else:
|
687
|
+
subject = self.description or self.name or ""
|
683
688
|
self.__get_subject_setter_participant().set_room_subject(
|
684
|
-
|
689
|
+
subject,
|
685
690
|
user_full_jid,
|
686
691
|
self.subject_date,
|
687
692
|
)
|
@@ -1331,6 +1336,17 @@ class LegacyMUC(
|
|
1331
1336
|
"""
|
1332
1337
|
raise NotImplementedError
|
1333
1338
|
|
1339
|
+
async def on_set_thread_subject(
|
1340
|
+
self, thread: LegacyThreadType, subject: str
|
1341
|
+
) -> None:
|
1342
|
+
"""
|
1343
|
+
Triggered when the user requests changing the subject of a specific thread.
|
1344
|
+
|
1345
|
+
:param thread: Legacy identifier of the thread
|
1346
|
+
:param subject: The new subject for this thread.
|
1347
|
+
"""
|
1348
|
+
raise NotImplementedError
|
1349
|
+
|
1334
1350
|
@property
|
1335
1351
|
def participants_filled(self) -> bool:
|
1336
1352
|
return self.stored.participants_filled
|
slidge/migration.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
import logging
|
2
|
-
import shutil
|
3
2
|
import sys
|
4
3
|
import traceback
|
5
4
|
from pathlib import Path
|
@@ -7,15 +6,6 @@ from pathlib import Path
|
|
7
6
|
from alembic import command
|
8
7
|
from alembic.config import Config
|
9
8
|
|
10
|
-
from .core import config
|
11
|
-
|
12
|
-
|
13
|
-
def remove_avatar_cache_v1() -> None:
|
14
|
-
old_dir = config.HOME_DIR / "slidge_avatars"
|
15
|
-
if old_dir.exists():
|
16
|
-
log.info("Avatar cache dir v1 found, clearing it.")
|
17
|
-
shutil.rmtree(old_dir)
|
18
|
-
|
19
9
|
|
20
10
|
def get_alembic_cfg() -> Config:
|
21
11
|
alembic_cfg = Config()
|
@@ -28,7 +18,6 @@ def get_alembic_cfg() -> Config:
|
|
28
18
|
|
29
19
|
|
30
20
|
def migrate() -> None:
|
31
|
-
remove_avatar_cache_v1()
|
32
21
|
try:
|
33
22
|
command.upgrade(get_alembic_cfg(), "head")
|
34
23
|
except Exception as e:
|
@@ -1,5 +1,6 @@
|
|
1
|
+
from slixmpp import register_stanza_plugin, __version_info__
|
1
2
|
from slixmpp.plugins.base import BasePlugin, register_plugin
|
2
|
-
from slixmpp.plugins.xep_0292.stanza import NS
|
3
|
+
from slixmpp.plugins.xep_0292.stanza import NS, _VCardTextElementBase, VCard4
|
3
4
|
|
4
5
|
|
5
6
|
class VCard4Provider(BasePlugin):
|
@@ -11,4 +12,13 @@ class VCard4Provider(BasePlugin):
|
|
11
12
|
self.xmpp.plugin["xep_0030"].add_feature(NS)
|
12
13
|
|
13
14
|
|
15
|
+
|
16
|
+
|
14
17
|
register_plugin(VCard4Provider)
|
18
|
+
|
19
|
+
|
20
|
+
if __version_info__[0] <= 1 and __version_info__[1] <= 11:
|
21
|
+
class Pronouns(_VCardTextElementBase):
|
22
|
+
name = plugin_attrib = "pronouns"
|
23
|
+
|
24
|
+
register_stanza_plugin(VCard4, Pronouns)
|
slidge/util/util.py
CHANGED
@@ -324,3 +324,10 @@ def strip_leading_emoji(text: str) -> str:
|
|
324
324
|
|
325
325
|
async def noop_coro() -> None:
|
326
326
|
pass
|
327
|
+
|
328
|
+
|
329
|
+
def add_quote_prefix(text: str):
|
330
|
+
"""
|
331
|
+
Return multi-line text with leading quote marks (i.e. the ">" character).
|
332
|
+
"""
|
333
|
+
return "\n".join(("> " + x).strip() for x in text.split("\n")).strip()
|
@@ -1,7 +1,7 @@
|
|
1
|
-
slidge/__init__.py,sha256=
|
1
|
+
slidge/__init__.py,sha256=SqHos3Dv_s6POnhYlZ2qLpEYB_sKITJrF0Wm2ORJ9ZI,1824
|
2
2
|
slidge/__main__.py,sha256=ydjUklOoavS4YlGfjRX_8BQN2DaSbaXPMi47RkOgcFI,37
|
3
3
|
slidge/main.py,sha256=bUdAau6TorsPpDRBPfzvUolHlXF5K-BY9aJdSmNUcyw,7109
|
4
|
-
slidge/migration.py,sha256=
|
4
|
+
slidge/migration.py,sha256=b0Bi1D6QObpBhwZy33LvtYy3ObIx56pUdxgLfoQglh4,1271
|
5
5
|
slidge/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
6
|
slidge/command/__init__.py,sha256=UYf1mjCYbZ5G7PIgaFTWSQRAzEJkQ6dTH8Fu_e_XnO0,613
|
7
7
|
slidge/command/adhoc.py,sha256=NCHammJdl30eHniiYukYQZKp1IeI-5qv5u9NncLNSNM,10760
|
@@ -10,19 +10,19 @@ slidge/command/base.py,sha256=h6lQ6ypb5uNrppZ_N8mS7ObJpnOINYZS6QokGlWVgAk,13527
|
|
10
10
|
slidge/command/categories.py,sha256=vF0KGDV9sEn8TNkcMoDRw-u3gEyNHSXghOU2JRHQtKs,351
|
11
11
|
slidge/command/chat_command.py,sha256=r_qalygOCsEoS-OgWw8IIlAzTufhXNjduONbeoepUIA,11557
|
12
12
|
slidge/command/register.py,sha256=BduDI31Kx8CbWWEdjybimTA5Wcfhn-Jkt8sSPsySCpo,6724
|
13
|
-
slidge/command/user.py,sha256=
|
13
|
+
slidge/command/user.py,sha256=xt7NTb8Fdj_Mx91BgiszSii6QF67LtsyowQ1vhBJSIU,12660
|
14
14
|
slidge/contact/__init__.py,sha256=WMMaHk7UW7YT9EH2LtPdkU0bHQaOp4ikBhbBQskmoc8,191
|
15
|
-
slidge/contact/contact.py,sha256=
|
15
|
+
slidge/contact/contact.py,sha256=uXHxfEsBNjTE0Tdk1s1_YETIazU03-pvaScd3QQmlu0,19607
|
16
16
|
slidge/contact/roster.py,sha256=uZ3iCl8oa7kEpIptUVa9p1k51bSvbuQq1pUGnf_zru8,9791
|
17
17
|
slidge/core/__init__.py,sha256=RG7Jj5JCJERjhqJ31lOLYV-7bH_oblClQD1KF9LsTXo,68
|
18
18
|
slidge/core/config.py,sha256=1HxHQ5BOnYghi8V5KbCQ6sUsNnXzAZAIoFA5PrMqH4E,6060
|
19
|
-
slidge/core/gateway.py,sha256=
|
20
|
-
slidge/core/pubsub.py,sha256
|
21
|
-
slidge/core/session.py,sha256=
|
19
|
+
slidge/core/gateway.py,sha256=5GImsd1b7KKcwIZcNSWgVieezrnp5dJCv7IlmOvWkRc,41192
|
20
|
+
slidge/core/pubsub.py,sha256=2Em3PvYz-PX-WM7ZqkEU9estNYCyQ--JdJq22DhrUlA,12145
|
21
|
+
slidge/core/session.py,sha256=leHLahwpI5_hqR6xv0juw_BQJnpuPqVmhOIwYEiPnx8,29861
|
22
22
|
slidge/core/dispatcher/__init__.py,sha256=1EXcjXietUKlxEqdrCWCV3xZ3q_DSsjHoqWrPMbtYao,84
|
23
23
|
slidge/core/dispatcher/caps.py,sha256=gISaHtFwFDXtkSrSsAkZfPiHQyXfmXg3v_YYU0w9iDg,2254
|
24
24
|
slidge/core/dispatcher/disco.py,sha256=xVPyBFnnkON-JjjM1oydRa-dqnDbwAZER2MGQACRhVk,2309
|
25
|
-
slidge/core/dispatcher/presence.py,sha256=
|
25
|
+
slidge/core/dispatcher/presence.py,sha256=3qGsiMGzTDnOpMbCRJ8RCrRGiSU-pAyOLJHphbZF5pw,7043
|
26
26
|
slidge/core/dispatcher/registration.py,sha256=Qa3fYZFJ4NaVz-FsnXorCmMQ9WyO7oZte1Zvro5f74E,3415
|
27
27
|
slidge/core/dispatcher/search.py,sha256=bL5cwMOtsfnX5IH-t60S1PudpeMWZnF4-qksrKDuDyc,3411
|
28
28
|
slidge/core/dispatcher/session_dispatcher.py,sha256=ysgPhns7NgUxhmkgEwRv-yDkSnUIXEdc-FsgqDqQAkE,3466
|
@@ -35,19 +35,19 @@ slidge/core/dispatcher/message/message.py,sha256=RK1sX4cBSvPZLiUGYoT-pdCB01ll176
|
|
35
35
|
slidge/core/dispatcher/muc/__init__.py,sha256=60YUr0i8PCZEPyNNTynJueRbbxF5pqzdyVf8z_XFXmM,290
|
36
36
|
slidge/core/dispatcher/muc/admin.py,sha256=1tDZ9hHD6q5SqCjsYOpDimPB3Iyl21YO5RnK1goEGso,3284
|
37
37
|
slidge/core/dispatcher/muc/mam.py,sha256=7vfmMI_mJOIrc9KCbtTibJSowhZTBBFwXWc84Ikpu3I,2994
|
38
|
-
slidge/core/dispatcher/muc/misc.py,sha256=
|
38
|
+
slidge/core/dispatcher/muc/misc.py,sha256=DgUCSVwcv7kD5xmkS59E-TGf9yWDZiu6NBHgVSdfgp0,4816
|
39
39
|
slidge/core/dispatcher/muc/owner.py,sha256=dDAxpRaA8H_NJQNIyBNixck2oG4GHZeEQqPhKd7MmDQ,3359
|
40
40
|
slidge/core/dispatcher/muc/ping.py,sha256=EgKKS9AvMnW-vINGcoGbtk6NdbN9A7zVaGfT5T7F6YE,1699
|
41
41
|
slidge/core/mixins/__init__.py,sha256=Zea39CCwjJU5XfHwcYPEZ9Sin8z1BZxoV68G2RwC3nE,386
|
42
42
|
slidge/core/mixins/attachment.py,sha256=6c1gZNydVZHgHB-_PjBLbT_9ntBSNG0AWqlv9Cgcku8,21906
|
43
43
|
slidge/core/mixins/avatar.py,sha256=0E0mQxdTUcJQrYXlBkYqkNl4bYuga4cIC1s4XA2rED8,5559
|
44
|
-
slidge/core/mixins/base.py,sha256=
|
45
|
-
slidge/core/mixins/db.py,sha256=
|
44
|
+
slidge/core/mixins/base.py,sha256=getXMptzJwIc4fEbeMoJCSKcC3awi8UbKnx5FVCthjc,783
|
45
|
+
slidge/core/mixins/db.py,sha256=lryMfRB-1-St2f7TZqW4sCl4xrWPWNlwgvn37T8wQPE,2678
|
46
46
|
slidge/core/mixins/disco.py,sha256=mrYhWO9qpnLMAVtKKqwbDh6CNOH2dPNERpyfmWzZGg8,3684
|
47
47
|
slidge/core/mixins/message.py,sha256=xk4bgiJF9ATe-rgtH4sHU8hUwecBF4KjGIujm90mbXQ,8014
|
48
|
-
slidge/core/mixins/message_maker.py,sha256=
|
49
|
-
slidge/core/mixins/message_text.py,sha256
|
50
|
-
slidge/core/mixins/presence.py,sha256=
|
48
|
+
slidge/core/mixins/message_maker.py,sha256=NaIQzpjYHKMuuqfqncviB6AE-q50-YhhstxZ-WhLWhE,6546
|
49
|
+
slidge/core/mixins/message_text.py,sha256=-hlGDzQ9dya-ZCPBe3v7UrpBetQa36N3YIACRxBt8Yc,9632
|
50
|
+
slidge/core/mixins/presence.py,sha256=DPNatkVIkw7GmHrkyUlUgsGhp0VUgaUgSBuhGalbe8c,9791
|
51
51
|
slidge/core/mixins/recipient.py,sha256=b0uFnpym-hOFgYxGjXT1xQcZ4YRbDSBftPcNWLzSwEI,1336
|
52
52
|
slidge/db/__init__.py,sha256=EBDH1JSEhgqYcli2Bw11CRC749wJk8AOucgBzmhDSvU,105
|
53
53
|
slidge/db/avatar.py,sha256=MXFd1oe0eL5CCUYbc5CpsIcbio3cY3xVoKt39RAoj9I,8240
|
@@ -59,10 +59,10 @@ slidge/db/alembic/env.py,sha256=hsBlRNs0zF5diSHGRSa8Fi3qRVQDA2rJdR41AEIdvxc,1642
|
|
59
59
|
slidge/db/alembic/script.py.mako,sha256=MEqL-2qATlST9TAOeYgscMn1uy6HUS9NFvDgl93dMj8,635
|
60
60
|
slidge/db/alembic/versions/cef02a8b1451_initial_schema.py,sha256=D1K-flfTM9Vkk0YFzg6HbmoDyEb2u5c751aXk2YzVVg,14881
|
61
61
|
slidge/group/__init__.py,sha256=yFt7cHqeaKIMN6f9ZyhhspOcJJvBtLedGv-iICG7lto,258
|
62
|
-
slidge/group/archive.py,sha256=
|
63
|
-
slidge/group/bookmarks.py,sha256=
|
64
|
-
slidge/group/participant.py,sha256=
|
65
|
-
slidge/group/room.py,sha256=
|
62
|
+
slidge/group/archive.py,sha256=gNHpGlUstPtBcnBgOarSYzkZHo9E8bNxl_ZY3wM6wHg,6108
|
63
|
+
slidge/group/bookmarks.py,sha256=eiXtlgirE59dBi1BT1349wCrGuHDkCoak1phCepzkqI,7653
|
64
|
+
slidge/group/participant.py,sha256=2yDJL2bL0w-EiWjq6V96UEhXUgmih5-GpKsoKfG2zdY,18734
|
65
|
+
slidge/group/room.py,sha256=lXzICLkv980o0JS-3HZTbvf2MD0ZlIw4-Y-xiU6lEew,49481
|
66
66
|
slidge/slixfix/__init__.py,sha256=LvaYZQYjr4l_45AYYpod1dB3MUaZye18vKF-4H8Bm20,4758
|
67
67
|
slidge/slixfix/delivery_receipt.py,sha256=JmogxsiXYEbTmCM4fvC5wkQs0jBsaJtKl4j_B_18riE,1415
|
68
68
|
slidge/slixfix/roster.py,sha256=DjjHQqCsKsPChUxV7S0Pm4IAgjfrwgm5tcTdJi3N_gY,1670
|
@@ -78,7 +78,7 @@ slidge/slixfix/xep_0100/stanza.py,sha256=7vCzej9VFQupsTpGGl0cJWuGNH4I6oVcckBu_-f
|
|
78
78
|
slidge/slixfix/xep_0153/__init__.py,sha256=hsEldnLuzvcp0NqSscxPV7FJl-6GFP372vlDg1G3S3I,283
|
79
79
|
slidge/slixfix/xep_0153/vcard_avatar.py,sha256=Oq4bV1XomedjIb9eWCGB5xxW_IDoRRYNTfQ-GYhzLYI,466
|
80
80
|
slidge/slixfix/xep_0292/__init__.py,sha256=_MvS9wGra6ig3P_dPAVlCPDJkiOFvUWGjaRsHj1woUg,98
|
81
|
-
slidge/slixfix/xep_0292/vcard4.py,sha256=
|
81
|
+
slidge/slixfix/xep_0292/vcard4.py,sha256=gC17q8TyV3YM37U8ei3od7HNP02GYH0YpSymjso6f6g,652
|
82
82
|
slidge/util/__init__.py,sha256=BELovoTMPcPPGz3D48esBr8A4BRRHXTvavfgnArBgEc,301
|
83
83
|
slidge/util/archive_msg.py,sha256=hGNquu38ouSWSc-kz_oAYPXwjhUVZNSedIpwkrXHSd0,1826
|
84
84
|
slidge/util/conf.py,sha256=Wv-xr1fQfz6jDCBpj2e5Nm-igMpdIjsYsVfoY8grJoo,7380
|
@@ -86,10 +86,10 @@ slidge/util/jid_escaping.py,sha256=QJ2Yj_j1gTmiO9g2r187iVCu7kia_O5ABhRiLAO2TG4,1
|
|
86
86
|
slidge/util/lock.py,sha256=ZnUi3LGiz271-YeYKo9JzxovJCoSwlP9P65pNyHIO9o,1029
|
87
87
|
slidge/util/test.py,sha256=_E6er2BtQlpyzTUmp4u8C9KhBYzbLrmTwSVgxGObKHU,13988
|
88
88
|
slidge/util/types.py,sha256=nilphTeJU3yb0MSqb86tZeWXis495oDvHSDDBs0hn_4,5747
|
89
|
-
slidge/util/util.py,sha256=
|
90
|
-
slidge-0.3.
|
91
|
-
slidge-0.3.
|
92
|
-
slidge-0.3.
|
93
|
-
slidge-0.3.
|
94
|
-
slidge-0.3.
|
95
|
-
slidge-0.3.
|
89
|
+
slidge/util/util.py,sha256=PBdHtcRIQi6Dy-yHASS_xiRGQ4Pv4i6QSSZAAELPnmQ,9536
|
90
|
+
slidge-0.3.1.dist-info/licenses/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
|
91
|
+
slidge-0.3.1.dist-info/METADATA,sha256=UEfy2L6fzCOoHu2yfUhlh0SI7cQW6LrC6XNhJySfYoo,5054
|
92
|
+
slidge-0.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
93
|
+
slidge-0.3.1.dist-info/entry_points.txt,sha256=py3_x834fFJ2TEzPd18Wt2DnysdAfuVqJ5zzBrXbAZs,44
|
94
|
+
slidge-0.3.1.dist-info/top_level.txt,sha256=2LRjDYHaGZ5ieCMF8xy58JIiabRMzX-MGMbCZwfE17c,7
|
95
|
+
slidge-0.3.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|