slidge 0.1.2__py3-none-any.whl → 0.2.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- slidge/__init__.py +3 -5
- slidge/__main__.py +2 -197
- slidge/__version__.py +5 -0
- slidge/command/adhoc.py +40 -17
- slidge/command/admin.py +24 -12
- slidge/command/base.py +10 -8
- slidge/command/categories.py +13 -3
- slidge/command/chat_command.py +29 -2
- slidge/command/register.py +32 -16
- slidge/command/user.py +106 -13
- slidge/contact/contact.py +254 -50
- slidge/contact/roster.py +124 -53
- slidge/core/config.py +19 -13
- slidge/core/dispatcher/__init__.py +3 -0
- slidge/core/{gateway → dispatcher}/caps.py +12 -8
- slidge/core/{gateway → dispatcher}/disco.py +10 -18
- slidge/core/dispatcher/message/__init__.py +10 -0
- slidge/core/dispatcher/message/chat_state.py +40 -0
- slidge/core/dispatcher/message/marker.py +62 -0
- slidge/core/dispatcher/message/message.py +397 -0
- slidge/core/dispatcher/muc/__init__.py +12 -0
- slidge/core/dispatcher/muc/admin.py +98 -0
- slidge/core/{gateway → dispatcher/muc}/mam.py +25 -17
- slidge/core/dispatcher/muc/misc.py +121 -0
- slidge/core/dispatcher/muc/owner.py +96 -0
- slidge/core/{gateway → dispatcher/muc}/ping.py +11 -17
- slidge/core/dispatcher/presence.py +176 -0
- slidge/core/dispatcher/registration.py +85 -0
- slidge/core/{gateway → dispatcher}/search.py +9 -16
- slidge/core/dispatcher/session_dispatcher.py +84 -0
- slidge/core/dispatcher/util.py +174 -0
- slidge/core/{gateway/vcard_temp.py → dispatcher/vcard.py} +35 -19
- slidge/core/{gateway/base.py → gateway.py} +176 -153
- slidge/core/mixins/__init__.py +11 -1
- slidge/core/mixins/attachment.py +106 -67
- slidge/core/mixins/avatar.py +94 -25
- slidge/core/mixins/base.py +10 -4
- slidge/core/mixins/db.py +18 -0
- slidge/core/mixins/disco.py +0 -10
- slidge/core/mixins/lock.py +10 -8
- slidge/core/mixins/message.py +11 -195
- slidge/core/mixins/message_maker.py +17 -9
- slidge/core/mixins/message_text.py +211 -0
- slidge/core/mixins/presence.py +17 -4
- slidge/core/pubsub.py +114 -288
- slidge/core/session.py +101 -40
- slidge/db/__init__.py +4 -0
- slidge/db/alembic/__init__.py +0 -0
- slidge/db/alembic/env.py +64 -0
- slidge/db/alembic/old_user_store.py +183 -0
- slidge/db/alembic/script.py.mako +26 -0
- slidge/db/alembic/versions/09f27f098baa_add_missing_attributes_in_room.py +36 -0
- slidge/db/alembic/versions/15b0bd83407a_remove_bogus_unique_constraints_on_room_.py +85 -0
- slidge/db/alembic/versions/2461390c0af2_store_contacts_caps_verstring_in_db.py +36 -0
- slidge/db/alembic/versions/29f5280c61aa_store_subject_setter_in_room.py +37 -0
- slidge/db/alembic/versions/2b1f45ab7379_store_room_subject_setter_by_nickname.py +41 -0
- slidge/db/alembic/versions/3071e0fa69d4_add_contact_client_type.py +52 -0
- slidge/db/alembic/versions/45c24cc73c91_add_bob.py +42 -0
- slidge/db/alembic/versions/5bd48bfdffa2_lift_room_legacy_id_constraint.py +61 -0
- slidge/db/alembic/versions/82a4af84b679_add_muc_history_filled.py +48 -0
- slidge/db/alembic/versions/8b993243a536_add_vcard_content_to_contact_table.py +43 -0
- slidge/db/alembic/versions/8d2ced764698_rely_on_db_to_store_contacts_rooms_and_.py +139 -0
- slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +101 -0
- slidge/db/alembic/versions/abba1ae0edb3_store_avatar_legacy_id_in_the_contact_.py +79 -0
- slidge/db/alembic/versions/b33993e87db3_move_everything_to_persistent_db.py +214 -0
- slidge/db/alembic/versions/b64b1a793483_add_source_and_legacy_id_for_archived_.py +52 -0
- slidge/db/alembic/versions/c4a8ec35a0e8_per_room_user_nick.py +34 -0
- slidge/db/alembic/versions/e91195719c2c_store_users_avatars_persistently.py +26 -0
- slidge/db/avatar.py +205 -0
- slidge/db/meta.py +72 -0
- slidge/db/models.py +405 -0
- slidge/db/store.py +1257 -0
- slidge/group/archive.py +58 -14
- slidge/group/bookmarks.py +89 -65
- slidge/group/participant.py +111 -44
- slidge/group/room.py +402 -213
- slidge/main.py +202 -0
- slidge/migration.py +45 -1
- slidge/slixfix/__init__.py +31 -1
- slidge/{core/gateway → slixfix}/delivery_receipt.py +1 -1
- slidge/slixfix/roster.py +13 -4
- slidge/slixfix/xep_0292/vcard4.py +1 -87
- slidge/util/archive_msg.py +2 -1
- slidge/util/db.py +4 -228
- slidge/util/test.py +91 -4
- slidge/util/types.py +39 -4
- slidge/util/util.py +45 -2
- {slidge-0.1.2.dist-info → slidge-0.2.0.dist-info}/METADATA +10 -5
- slidge-0.2.0.dist-info/RECORD +131 -0
- slidge-0.2.0.dist-info/entry_points.txt +3 -0
- slidge/core/cache.py +0 -183
- slidge/core/gateway/__init__.py +0 -3
- slidge/core/gateway/muc_admin.py +0 -35
- slidge/core/gateway/presence.py +0 -95
- slidge/core/gateway/registration.py +0 -53
- slidge/core/gateway/session_dispatcher.py +0 -795
- slidge/util/schema.sql +0 -126
- slidge/util/sql.py +0 -508
- slidge-0.1.2.dist-info/RECORD +0 -96
- slidge-0.1.2.dist-info/entry_points.txt +0 -3
- {slidge-0.1.2.dist-info → slidge-0.2.0.dist-info}/LICENSE +0 -0
- {slidge-0.1.2.dist-info → slidge-0.2.0.dist-info}/WHEEL +0 -0
slidge/util/db.py
CHANGED
@@ -1,229 +1,5 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
"""
|
1
|
+
# here to allow migration of the user store from v0.1
|
2
|
+
# since it relies on shelf, which relies on pickle, we need to keep objects
|
3
|
+
# importable where they were when the shelf was written
|
5
4
|
|
6
|
-
import
|
7
|
-
import datetime
|
8
|
-
import logging
|
9
|
-
import os.path
|
10
|
-
import shelve
|
11
|
-
from io import BytesIO
|
12
|
-
from os import PathLike
|
13
|
-
from typing import Iterable, Optional, Union
|
14
|
-
|
15
|
-
from pickle_secure import Pickler, Unpickler
|
16
|
-
from slixmpp import JID, Iq, Message, Presence
|
17
|
-
|
18
|
-
from .sql import db
|
19
|
-
|
20
|
-
|
21
|
-
# noinspection PyUnresolvedReferences
|
22
|
-
class EncryptedShelf(shelve.DbfilenameShelf):
|
23
|
-
cache: dict
|
24
|
-
dict: dict
|
25
|
-
writeback: bool
|
26
|
-
keyencoding: str
|
27
|
-
_protocol: int
|
28
|
-
|
29
|
-
def __init__(
|
30
|
-
self, filename: PathLike, key: str, flag="c", protocol=None, writeback=False
|
31
|
-
):
|
32
|
-
super().__init__(str(filename), flag, protocol, writeback)
|
33
|
-
self.secret_key = key
|
34
|
-
|
35
|
-
def __getitem__(self, key):
|
36
|
-
try:
|
37
|
-
value = self.cache[key]
|
38
|
-
except KeyError:
|
39
|
-
f = BytesIO(self.dict[key.encode(self.keyencoding)])
|
40
|
-
value = Unpickler(f, key=self.secret_key).load() # type:ignore
|
41
|
-
if self.writeback:
|
42
|
-
self.cache[key] = value
|
43
|
-
return value
|
44
|
-
|
45
|
-
def __setitem__(self, key, value):
|
46
|
-
if self.writeback:
|
47
|
-
self.cache[key] = value
|
48
|
-
f = BytesIO()
|
49
|
-
p = Pickler(f, self._protocol, key=self.secret_key) # type:ignore
|
50
|
-
p.dump(value)
|
51
|
-
self.dict[key.encode(self.keyencoding)] = f.getvalue()
|
52
|
-
|
53
|
-
|
54
|
-
@dataclasses.dataclass
|
55
|
-
class GatewayUser:
|
56
|
-
"""
|
57
|
-
A gateway user
|
58
|
-
"""
|
59
|
-
|
60
|
-
bare_jid: str
|
61
|
-
"""Bare JID of the user"""
|
62
|
-
registration_form: dict[str, Optional[str]]
|
63
|
-
"""Content of the registration form, as a dict"""
|
64
|
-
plugin_data: Optional[dict] = None
|
65
|
-
registration_date: Optional[datetime.datetime] = None
|
66
|
-
|
67
|
-
def __hash__(self):
|
68
|
-
return hash(self.bare_jid)
|
69
|
-
|
70
|
-
def __repr__(self):
|
71
|
-
return f"<User {self.bare_jid}>"
|
72
|
-
|
73
|
-
def __post_init__(self):
|
74
|
-
if self.registration_date is None:
|
75
|
-
self.registration_date = datetime.datetime.now()
|
76
|
-
|
77
|
-
@property
|
78
|
-
def jid(self) -> JID:
|
79
|
-
"""
|
80
|
-
The user's (bare) JID
|
81
|
-
|
82
|
-
:return:
|
83
|
-
"""
|
84
|
-
return JID(self.bare_jid)
|
85
|
-
|
86
|
-
def get(self, field: str, default: str = "") -> Optional[str]:
|
87
|
-
# """
|
88
|
-
# Get fields from the registration form (required to comply with slixmpp backend protocol)
|
89
|
-
#
|
90
|
-
# :param field: Name of the field
|
91
|
-
# :param default: Default value to return if the field is not present
|
92
|
-
#
|
93
|
-
# :return: Value of the field
|
94
|
-
# """
|
95
|
-
return self.registration_form.get(field, default)
|
96
|
-
|
97
|
-
def commit(self):
|
98
|
-
db.user_store(self)
|
99
|
-
user_store.commit(self)
|
100
|
-
|
101
|
-
|
102
|
-
class UserStore:
|
103
|
-
"""
|
104
|
-
Basic user store implementation using shelve from the python standard library
|
105
|
-
|
106
|
-
Set_file must be called before it is usable
|
107
|
-
"""
|
108
|
-
|
109
|
-
def __init__(self):
|
110
|
-
self._users: shelve.Shelf[GatewayUser] = None # type: ignore
|
111
|
-
|
112
|
-
def set_file(self, filename: PathLike, secret_key: Optional[str] = None):
|
113
|
-
"""
|
114
|
-
Set the file to use to store user data
|
115
|
-
|
116
|
-
:param filename: Path to the shelf file
|
117
|
-
:param secret_key: Secret key to store files encrypted on disk
|
118
|
-
"""
|
119
|
-
if self._users is not None:
|
120
|
-
raise RuntimeError("Shelf file already set!")
|
121
|
-
if os.path.exists(filename):
|
122
|
-
log.info("Using existing slidge DB: %s", filename)
|
123
|
-
else:
|
124
|
-
log.info("Creating a new slidge DB: %s", filename)
|
125
|
-
if secret_key:
|
126
|
-
self._users = EncryptedShelf(filename, key=secret_key)
|
127
|
-
else:
|
128
|
-
self._users = shelve.open(str(filename))
|
129
|
-
for user in self._users.values():
|
130
|
-
db.user_store(user)
|
131
|
-
log.info("Registered users in the DB: %s", list(self._users.keys()))
|
132
|
-
|
133
|
-
def get_all(self) -> Iterable[GatewayUser]:
|
134
|
-
"""
|
135
|
-
Get all users in the store
|
136
|
-
|
137
|
-
:return: An iterable of GatewayUsers
|
138
|
-
"""
|
139
|
-
return self._users.values()
|
140
|
-
|
141
|
-
def add(self, jid: JID, registration_form: dict[str, Optional[str]]):
|
142
|
-
"""
|
143
|
-
Add a user to the store.
|
144
|
-
|
145
|
-
NB: there is no reason to call this manually, as this should be covered
|
146
|
-
by slixmpp XEP-0077 and XEP-0100 plugins
|
147
|
-
|
148
|
-
:param jid: JID of the gateway user
|
149
|
-
:param registration_form: Content of the registration form (:xep:`0077`)
|
150
|
-
"""
|
151
|
-
log.debug("Adding user %s", jid)
|
152
|
-
self._users[jid.bare] = user = GatewayUser(
|
153
|
-
bare_jid=jid.bare,
|
154
|
-
registration_form=registration_form,
|
155
|
-
registration_date=datetime.datetime.now(),
|
156
|
-
)
|
157
|
-
self._users.sync()
|
158
|
-
user.commit()
|
159
|
-
log.debug("Store: %s", self._users)
|
160
|
-
|
161
|
-
def commit(self, user: GatewayUser):
|
162
|
-
self._users[user.bare_jid] = user
|
163
|
-
self._users.sync()
|
164
|
-
|
165
|
-
def get(self, _gateway_jid, _node, ifrom: JID, iq) -> Optional[GatewayUser]:
|
166
|
-
"""
|
167
|
-
Get a user from the store
|
168
|
-
|
169
|
-
NB: there is no reason to call this, it is used by SliXMPP internal API
|
170
|
-
|
171
|
-
:param _gateway_jid:
|
172
|
-
:param _node:
|
173
|
-
:param ifrom:
|
174
|
-
:param iq:
|
175
|
-
:return:
|
176
|
-
"""
|
177
|
-
if ifrom is None: # bug in SliXMPP's XEP_0100 plugin
|
178
|
-
ifrom = iq["from"]
|
179
|
-
log.debug("Getting user %s", ifrom.bare)
|
180
|
-
return self._users.get(ifrom.bare)
|
181
|
-
|
182
|
-
def remove(self, _gateway_jid, _node, ifrom: JID, _iq):
|
183
|
-
"""
|
184
|
-
Remove a user from the store
|
185
|
-
|
186
|
-
NB: there is no reason to call this, it is used by SliXMPP internal API
|
187
|
-
"""
|
188
|
-
self.remove_by_jid(ifrom)
|
189
|
-
|
190
|
-
def remove_by_jid(self, jid: JID):
|
191
|
-
"""
|
192
|
-
Remove a user from the store, by JID
|
193
|
-
"""
|
194
|
-
j = jid.bare
|
195
|
-
log.debug("Removing user %s", j)
|
196
|
-
db.user_del(self._users[j])
|
197
|
-
del self._users[j]
|
198
|
-
self._users.sync()
|
199
|
-
|
200
|
-
def get_by_jid(self, jid: JID) -> Optional[GatewayUser]:
|
201
|
-
"""
|
202
|
-
Convenience function to get a user from their JID.
|
203
|
-
|
204
|
-
:param jid: JID of the gateway user
|
205
|
-
:return:
|
206
|
-
"""
|
207
|
-
return self._users.get(jid.bare)
|
208
|
-
|
209
|
-
def get_by_stanza(self, s: Union[Presence, Message, Iq]) -> Optional[GatewayUser]:
|
210
|
-
"""
|
211
|
-
Convenience function to get a user from a stanza they sent.
|
212
|
-
|
213
|
-
:param s: A stanza sent by the gateway user
|
214
|
-
:return:
|
215
|
-
"""
|
216
|
-
return self.get_by_jid(s.get_from())
|
217
|
-
|
218
|
-
def close(self):
|
219
|
-
self._users.sync()
|
220
|
-
self._users.close()
|
221
|
-
|
222
|
-
|
223
|
-
user_store = UserStore()
|
224
|
-
"""
|
225
|
-
A persistent store for slidge users. Not public, but I didn't find how to hide
|
226
|
-
it from the docs!
|
227
|
-
"""
|
228
|
-
|
229
|
-
log = logging.getLogger(__name__)
|
5
|
+
from ..db.alembic.old_user_store import * # noqa:F403
|
slidge/util/test.py
CHANGED
@@ -7,6 +7,7 @@ from xml.dom.minidom import parseString
|
|
7
7
|
|
8
8
|
import xmldiff.main
|
9
9
|
from slixmpp import (
|
10
|
+
JID,
|
10
11
|
ElementBase,
|
11
12
|
Iq,
|
12
13
|
MatcherId,
|
@@ -20,6 +21,7 @@ from slixmpp.stanza.error import Error
|
|
20
21
|
from slixmpp.test import SlixTest, TestTransport
|
21
22
|
from slixmpp.xmlstream import highlight, tostring
|
22
23
|
from slixmpp.xmlstream.matcher import MatchIDSender
|
24
|
+
from sqlalchemy import create_engine, delete
|
23
25
|
|
24
26
|
from slidge import (
|
25
27
|
BaseGateway,
|
@@ -29,12 +31,16 @@ from slidge import (
|
|
29
31
|
LegacyMUC,
|
30
32
|
LegacyParticipant,
|
31
33
|
LegacyRoster,
|
32
|
-
user_store,
|
33
34
|
)
|
34
35
|
|
35
36
|
from ..command import Command
|
36
37
|
from ..core import config
|
37
38
|
from ..core.config import _TimedeltaSeconds
|
39
|
+
from ..core.pubsub import PepAvatar, PepNick
|
40
|
+
from ..db import SlidgeStore
|
41
|
+
from ..db.avatar import avatar_cache
|
42
|
+
from ..db.meta import Base
|
43
|
+
from ..db.models import Contact
|
38
44
|
|
39
45
|
|
40
46
|
class SlixTestPlus(SlixTest):
|
@@ -187,10 +193,10 @@ class SlidgeTest(SlixTestPlus):
|
|
187
193
|
no_roster_push = False
|
188
194
|
upload_requester = None
|
189
195
|
ignore_delay_threshold = _TimedeltaSeconds("300")
|
196
|
+
last_seen_fallback = True
|
190
197
|
|
191
198
|
@classmethod
|
192
199
|
def setUpClass(cls):
|
193
|
-
user_store.set_file(Path(tempfile.mkdtemp()) / "test.db")
|
194
200
|
for k, v in vars(cls.Config).items():
|
195
201
|
setattr(config, k.upper(), v)
|
196
202
|
|
@@ -209,9 +215,27 @@ class SlidgeTest(SlixTestPlus):
|
|
209
215
|
self.plugin, LegacyBookmarks, base_ok=True
|
210
216
|
)
|
211
217
|
|
212
|
-
|
218
|
+
# workaround for duplicate output of sql alchemy's log, cf
|
219
|
+
# https://stackoverflow.com/a/76498428/5902284
|
220
|
+
from sqlalchemy import log as sqlalchemy_log
|
213
221
|
|
222
|
+
sqlalchemy_log._add_default_handler = lambda x: None
|
223
|
+
|
224
|
+
engine = self.db_engine = create_engine("sqlite+pysqlite:///:memory:")
|
225
|
+
Base.metadata.create_all(engine)
|
226
|
+
BaseGateway.store = SlidgeStore(engine)
|
227
|
+
BaseGateway._test_mode = True
|
228
|
+
try:
|
229
|
+
self.xmpp = BaseGateway.get_self_or_unique_subclass()()
|
230
|
+
except Exception:
|
231
|
+
raise
|
232
|
+
self.xmpp.TEST_MODE = True
|
233
|
+
PepNick.contact_store = self.xmpp.store.contacts
|
234
|
+
PepAvatar.store = self.xmpp.store
|
235
|
+
avatar_cache.store = self.xmpp.store.avatars
|
236
|
+
avatar_cache.set_dir(Path(tempfile.mkdtemp()))
|
214
237
|
self.xmpp._always_send_everything = True
|
238
|
+
engine.echo = True
|
215
239
|
|
216
240
|
self.xmpp.connection_made(TestTransport(self.xmpp))
|
217
241
|
self.xmpp.session_bind_event.set()
|
@@ -242,10 +266,73 @@ class SlidgeTest(SlixTestPlus):
|
|
242
266
|
self.xmpp.use_presence_ids = False
|
243
267
|
Error.namespace = "jabber:component:accept"
|
244
268
|
|
269
|
+
def tearDown(self):
|
270
|
+
self.db_engine.echo = False
|
271
|
+
super().tearDown()
|
272
|
+
import slidge.db.store
|
273
|
+
|
274
|
+
if slidge.db.store._session is not None:
|
275
|
+
slidge.db.store._session.commit()
|
276
|
+
slidge.db.store._session = None
|
277
|
+
Base.metadata.drop_all(self.xmpp.store._engine)
|
278
|
+
|
279
|
+
def setup_logged_session(self, n_contacts=0):
|
280
|
+
user = self.xmpp.store.users.new(
|
281
|
+
JID("romeo@montague.lit/gajim"), {"username": "romeo", "city": ""}
|
282
|
+
)
|
283
|
+
user.preferences = {"sync_avatar": True, "sync_presence": True}
|
284
|
+
self.xmpp.store.users.update(user)
|
285
|
+
|
286
|
+
with self.xmpp.store.session() as session:
|
287
|
+
session.execute(delete(Contact))
|
288
|
+
session.commit()
|
289
|
+
|
290
|
+
self.run_coro(
|
291
|
+
self.xmpp._BaseGateway__dispatcher._on_user_register(
|
292
|
+
Iq(sfrom="romeo@montague.lit/gajim")
|
293
|
+
)
|
294
|
+
)
|
295
|
+
welcome = self.next_sent()
|
296
|
+
assert welcome["body"], welcome
|
297
|
+
stanza = self.next_sent()
|
298
|
+
assert "logging in" in stanza["status"].lower(), stanza
|
299
|
+
stanza = self.next_sent()
|
300
|
+
assert "syncing contacts" in stanza["status"].lower(), stanza
|
301
|
+
if BaseGateway.get_self_or_unique_subclass().GROUPS:
|
302
|
+
stanza = self.next_sent()
|
303
|
+
assert "syncing groups" in stanza["status"].lower(), stanza
|
304
|
+
for _ in range(n_contacts):
|
305
|
+
probe = self.next_sent()
|
306
|
+
assert probe.get_type() == "probe"
|
307
|
+
stanza = self.next_sent()
|
308
|
+
assert "yup" in stanza["status"].lower(), stanza
|
309
|
+
self.romeo: BaseSession = BaseSession.get_self_or_unique_subclass().from_jid(
|
310
|
+
JID("romeo@montague.lit")
|
311
|
+
)
|
312
|
+
|
313
|
+
self.juliet: LegacyContact = self.run_coro(
|
314
|
+
self.romeo.contacts.by_legacy_id("juliet")
|
315
|
+
)
|
316
|
+
self.room: LegacyMUC = self.run_coro(self.romeo.bookmarks.by_legacy_id("room"))
|
317
|
+
self.first_witch: LegacyParticipant = self.run_coro(
|
318
|
+
self.room.get_participant("firstwitch")
|
319
|
+
)
|
320
|
+
self.send( # language=XML
|
321
|
+
"""
|
322
|
+
<iq type="get"
|
323
|
+
to="romeo@montague.lit"
|
324
|
+
id="1"
|
325
|
+
from="aim.shakespeare.lit">
|
326
|
+
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
327
|
+
<items node="urn:xmpp:avatar:metadata" />
|
328
|
+
</pubsub>
|
329
|
+
</iq>
|
330
|
+
"""
|
331
|
+
)
|
332
|
+
|
245
333
|
@classmethod
|
246
334
|
def tearDownClass(cls):
|
247
335
|
reset_subclasses()
|
248
|
-
user_store._users = None
|
249
336
|
|
250
337
|
|
251
338
|
def format_stanza(stanza):
|
slidge/util/types.py
CHANGED
@@ -3,6 +3,7 @@ Typing stuff
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
from dataclasses import dataclass
|
6
|
+
from datetime import datetime
|
6
7
|
from enum import IntEnum
|
7
8
|
from pathlib import Path
|
8
9
|
from typing import (
|
@@ -20,14 +21,13 @@ from typing import (
|
|
20
21
|
)
|
21
22
|
|
22
23
|
from slixmpp import Message, Presence
|
23
|
-
from slixmpp.types import PresenceShows
|
24
|
+
from slixmpp.types import PresenceShows, PresenceTypes
|
24
25
|
|
25
26
|
if TYPE_CHECKING:
|
26
27
|
from ..contact import LegacyContact
|
27
28
|
from ..core.pubsub import PepItem
|
28
29
|
from ..core.session import BaseSession
|
29
30
|
from ..group.participant import LegacyMUC, LegacyParticipant
|
30
|
-
from .db import GatewayUser
|
31
31
|
|
32
32
|
AnyBaseSession = BaseSession[Any, Any]
|
33
33
|
else:
|
@@ -75,6 +75,10 @@ FieldType = Literal[
|
|
75
75
|
]
|
76
76
|
MucAffiliation = Literal["owner", "admin", "member", "outcast", "none"]
|
77
77
|
MucRole = Literal["visitor", "participant", "moderator", "none"]
|
78
|
+
# https://xmpp.org/registrar/disco-categories.html#client
|
79
|
+
ClientType = Literal[
|
80
|
+
"bot", "console", "game", "handheld", "pc", "phone", "sms", "tablet", "web"
|
81
|
+
]
|
78
82
|
|
79
83
|
|
80
84
|
@dataclass
|
@@ -84,13 +88,16 @@ class MessageReference(Generic[LegacyMessageType]):
|
|
84
88
|
|
85
89
|
At the very minimum, the legacy message ID attribute must be set, but to
|
86
90
|
ensure that the quote is displayed in all XMPP clients, the author must also
|
87
|
-
be set
|
91
|
+
be set (use the string "user" if the slidge user is the author of the referenced
|
92
|
+
message).
|
88
93
|
The body is used as a fallback for XMPP clients that do not support :xep:`0461`
|
89
94
|
of that failed to find the referenced message.
|
90
95
|
"""
|
91
96
|
|
92
97
|
legacy_id: LegacyMessageType
|
93
|
-
author: Optional[Union["
|
98
|
+
author: Optional[Union[Literal["user"], "LegacyParticipant", "LegacyContact"]] = (
|
99
|
+
None
|
100
|
+
)
|
94
101
|
body: Optional[str] = None
|
95
102
|
|
96
103
|
|
@@ -178,3 +185,31 @@ class Mention(NamedTuple):
|
|
178
185
|
class Hat(NamedTuple):
|
179
186
|
uri: str
|
180
187
|
title: str
|
188
|
+
|
189
|
+
|
190
|
+
class UserPreferences(TypedDict):
|
191
|
+
sync_avatar: bool
|
192
|
+
sync_presence: bool
|
193
|
+
|
194
|
+
|
195
|
+
class MamMetadata(NamedTuple):
|
196
|
+
id: str
|
197
|
+
sent_on: datetime
|
198
|
+
|
199
|
+
|
200
|
+
class HoleBound(NamedTuple):
|
201
|
+
id: int | str
|
202
|
+
timestamp: datetime
|
203
|
+
|
204
|
+
|
205
|
+
class CachedPresence(NamedTuple):
|
206
|
+
last_seen: Optional[datetime] = None
|
207
|
+
ptype: Optional[PresenceTypes] = None
|
208
|
+
pstatus: Optional[str] = None
|
209
|
+
pshow: Optional[PresenceShows] = None
|
210
|
+
|
211
|
+
|
212
|
+
class Sticker(NamedTuple):
|
213
|
+
path: Path
|
214
|
+
content_type: Optional[str]
|
215
|
+
hashes: dict[str, str]
|
slidge/util/util.py
CHANGED
@@ -4,8 +4,17 @@ import re
|
|
4
4
|
import subprocess
|
5
5
|
import warnings
|
6
6
|
from abc import ABCMeta
|
7
|
+
from functools import wraps
|
7
8
|
from pathlib import Path
|
8
|
-
from
|
9
|
+
from time import time
|
10
|
+
from typing import TYPE_CHECKING, Callable, NamedTuple, Optional, Type, TypeVar
|
11
|
+
|
12
|
+
try:
|
13
|
+
import emoji
|
14
|
+
except ImportError:
|
15
|
+
EMOJI_LIB_AVAILABLE = False
|
16
|
+
else:
|
17
|
+
EMOJI_LIB_AVAILABLE = True
|
9
18
|
|
10
19
|
from .types import Mention, ResourceDict
|
11
20
|
|
@@ -274,7 +283,10 @@ def deprecated(name: str, new: Callable):
|
|
274
283
|
return wrapped
|
275
284
|
|
276
285
|
|
277
|
-
|
286
|
+
T = TypeVar("T", bound=NamedTuple)
|
287
|
+
|
288
|
+
|
289
|
+
def dict_to_named_tuple(data: dict, cls: Type[T]) -> T:
|
278
290
|
return cls(*(data.get(f) for f in cls._fields)) # type:ignore
|
279
291
|
|
280
292
|
|
@@ -293,3 +305,34 @@ def replace_mentions(
|
|
293
305
|
cursor = mention.end
|
294
306
|
pieces.append(text[cursor:])
|
295
307
|
return "".join(pieces)
|
308
|
+
|
309
|
+
|
310
|
+
def with_session(func):
|
311
|
+
@wraps(func)
|
312
|
+
async def wrapped(self, *args, **kwargs):
|
313
|
+
with self.xmpp.store.session():
|
314
|
+
return await func(self, *args, **kwargs)
|
315
|
+
|
316
|
+
return wrapped
|
317
|
+
|
318
|
+
|
319
|
+
def timeit(func):
|
320
|
+
@wraps(func)
|
321
|
+
async def wrapped(self, *args, **kwargs):
|
322
|
+
start = time()
|
323
|
+
r = await func(self, *args, **kwargs)
|
324
|
+
self.log.info("%s took %s ms", func.__name__, round((time() - start) * 1000))
|
325
|
+
return r
|
326
|
+
|
327
|
+
return wrapped
|
328
|
+
|
329
|
+
|
330
|
+
def strip_leading_emoji(text: str) -> str:
|
331
|
+
if not EMOJI_LIB_AVAILABLE:
|
332
|
+
return text
|
333
|
+
words = text.split(" ")
|
334
|
+
# is_emoji returns False for 🛷️ for obscure reasons,
|
335
|
+
# purely_emoji seems better
|
336
|
+
if len(words) > 1 and emoji.purely_emoji(words[0]):
|
337
|
+
return " ".join(words[1:])
|
338
|
+
return text
|
@@ -1,25 +1,30 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: slidge
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.2.0
|
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
|
13
|
+
Classifier: Topic :: Internet :: XMPP
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
15
15
|
Requires-Dist: ConfigArgParse (>=1.5.3,<2.0.0)
|
16
16
|
Requires-Dist: Pillow (>=10,<11)
|
17
17
|
Requires-Dist: aiohttp[speedups] (>=3.8.3,<4.0.0)
|
18
|
-
Requires-Dist:
|
18
|
+
Requires-Dist: alembic (>=1.13.1,<2.0.0)
|
19
|
+
Requires-Dist: defusedxml (>=0.7.1,<0.8.0)
|
19
20
|
Requires-Dist: pickle-secure (>=0.99.9,<0.100.0)
|
20
21
|
Requires-Dist: python-magic (>=0.4.27,<0.5.0)
|
21
22
|
Requires-Dist: qrcode (>=7.4.1,<8.0.0)
|
22
23
|
Requires-Dist: slixmpp (>=1.8.5,<2.0.0)
|
24
|
+
Requires-Dist: sqlalchemy (>=2.0.29,<3.0.0)
|
25
|
+
Requires-Dist: thumbhash (>=0.1.2,<0.2.0)
|
26
|
+
Project-URL: Bug tracker, https://todo.sr.ht/~nicoco/slidge
|
27
|
+
Project-URL: Chat room, https://conference.nicoco.fr:5281/muc_log/slidge/
|
23
28
|
Project-URL: Documentation, https://slidge.im/
|
24
29
|
Project-URL: Repository, https://git.sr.ht/~nicoco/slidge/
|
25
30
|
Description-Content-Type: text/markdown
|