slidge 0.1.3__py3-none-any.whl → 0.2.0a1__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 +3 -5
- slidge/__main__.py +2 -196
- slidge/__version__.py +5 -0
- slidge/command/adhoc.py +8 -1
- slidge/command/admin.py +6 -7
- slidge/command/base.py +1 -2
- slidge/command/register.py +32 -16
- slidge/command/user.py +85 -6
- slidge/contact/contact.py +165 -49
- slidge/contact/roster.py +122 -47
- slidge/core/config.py +14 -11
- slidge/core/gateway/base.py +148 -36
- slidge/core/gateway/caps.py +7 -5
- slidge/core/gateway/disco.py +2 -4
- slidge/core/gateway/mam.py +1 -4
- slidge/core/gateway/muc_admin.py +1 -1
- slidge/core/gateway/ping.py +2 -3
- slidge/core/gateway/presence.py +1 -1
- slidge/core/gateway/registration.py +32 -21
- slidge/core/gateway/search.py +3 -5
- slidge/core/gateway/session_dispatcher.py +120 -57
- slidge/core/gateway/vcard_temp.py +7 -5
- slidge/core/mixins/__init__.py +11 -1
- slidge/core/mixins/attachment.py +32 -14
- slidge/core/mixins/avatar.py +90 -25
- slidge/core/mixins/base.py +8 -2
- slidge/core/mixins/db.py +18 -0
- slidge/core/mixins/disco.py +0 -10
- slidge/core/mixins/message.py +18 -8
- slidge/core/mixins/message_maker.py +17 -9
- slidge/core/mixins/presence.py +17 -4
- slidge/core/pubsub.py +54 -220
- slidge/core/session.py +69 -34
- slidge/db/__init__.py +4 -0
- slidge/db/alembic/env.py +64 -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/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/82a4af84b679_add_muc_history_filled.py +48 -0
- slidge/db/alembic/versions/8d2ced764698_rely_on_db_to_store_contacts_rooms_and_.py +133 -0
- slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +85 -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 +48 -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 +235 -0
- slidge/db/meta.py +65 -0
- slidge/db/models.py +375 -0
- slidge/db/store.py +1078 -0
- slidge/group/archive.py +58 -14
- slidge/group/bookmarks.py +72 -57
- slidge/group/participant.py +87 -28
- slidge/group/room.py +369 -211
- slidge/main.py +201 -0
- slidge/migration.py +30 -0
- slidge/slixfix/__init__.py +35 -2
- slidge/slixfix/roster.py +11 -4
- slidge/slixfix/xep_0292/vcard4.py +3 -0
- slidge/util/archive_msg.py +2 -1
- slidge/util/db.py +1 -47
- slidge/util/test.py +71 -4
- slidge/util/types.py +29 -4
- slidge/util/util.py +22 -0
- {slidge-0.1.3.dist-info → slidge-0.2.0a1.dist-info}/METADATA +4 -4
- slidge-0.2.0a1.dist-info/RECORD +114 -0
- slidge/core/cache.py +0 -183
- slidge/util/schema.sql +0 -126
- slidge/util/sql.py +0 -508
- slidge-0.1.3.dist-info/RECORD +0 -96
- {slidge-0.1.3.dist-info → slidge-0.2.0a1.dist-info}/LICENSE +0 -0
- {slidge-0.1.3.dist-info → slidge-0.2.0a1.dist-info}/WHEEL +0 -0
- {slidge-0.1.3.dist-info → slidge-0.2.0a1.dist-info}/entry_points.txt +0 -0
slidge/main.py
ADDED
@@ -0,0 +1,201 @@
|
|
1
|
+
"""
|
2
|
+
Slidge can be configured via CLI args, environment variables and/or INI files.
|
3
|
+
|
4
|
+
To use env vars, use this convention: ``--home-dir`` becomes ``HOME_DIR``.
|
5
|
+
|
6
|
+
Everything in ``/etc/slidge/conf.d/*`` is automatically used.
|
7
|
+
To use a plugin-specific INI file, put it in another dir,
|
8
|
+
and launch slidge with ``-c /path/to/plugin-specific.conf``.
|
9
|
+
Use the long version of the CLI arg without the double dash prefix inside this
|
10
|
+
INI file, eg ``debug=true``.
|
11
|
+
|
12
|
+
An example configuration file is available at
|
13
|
+
https://git.sr.ht/~nicoco/slidge/tree/master/item/dev/confs/slidge-example.ini
|
14
|
+
"""
|
15
|
+
|
16
|
+
import asyncio
|
17
|
+
import importlib
|
18
|
+
import logging
|
19
|
+
import os
|
20
|
+
import re
|
21
|
+
import signal
|
22
|
+
from pathlib import Path
|
23
|
+
|
24
|
+
import configargparse
|
25
|
+
|
26
|
+
from slidge import BaseGateway
|
27
|
+
from slidge.__version__ import __version__
|
28
|
+
from slidge.core import config
|
29
|
+
from slidge.core.pubsub import PepAvatar, PepNick
|
30
|
+
from slidge.db import SlidgeStore
|
31
|
+
from slidge.db.avatar import avatar_cache
|
32
|
+
from slidge.db.meta import get_engine
|
33
|
+
from slidge.migration import migrate
|
34
|
+
from slidge.util.conf import ConfigModule
|
35
|
+
from slidge.util.db import user_store
|
36
|
+
|
37
|
+
|
38
|
+
class MainConfig(ConfigModule):
|
39
|
+
def update_dynamic_defaults(self, args):
|
40
|
+
# force=True is needed in case we call a logger before this is reached,
|
41
|
+
# or basicConfig has no effect
|
42
|
+
logging.basicConfig(
|
43
|
+
level=args.loglevel,
|
44
|
+
filename=args.log_file,
|
45
|
+
force=True,
|
46
|
+
format=args.log_format,
|
47
|
+
)
|
48
|
+
|
49
|
+
if args.home_dir is None:
|
50
|
+
args.home_dir = Path("/var/lib/slidge") / str(args.jid)
|
51
|
+
|
52
|
+
if args.user_jid_validator is None:
|
53
|
+
args.user_jid_validator = ".*@" + re.escape(args.server)
|
54
|
+
|
55
|
+
if args.db_url is None:
|
56
|
+
args.db_url = f"sqlite:///{args.home_dir}/slidge.sqlite"
|
57
|
+
|
58
|
+
|
59
|
+
class SigTermInterrupt(Exception):
|
60
|
+
pass
|
61
|
+
|
62
|
+
|
63
|
+
def get_configurator():
|
64
|
+
p = configargparse.ArgumentParser(
|
65
|
+
default_config_files=os.getenv(
|
66
|
+
"SLIDGE_CONF_DIR", "/etc/slidge/conf.d/*.conf"
|
67
|
+
).split(":"),
|
68
|
+
description=__doc__,
|
69
|
+
)
|
70
|
+
p.add_argument(
|
71
|
+
"-c",
|
72
|
+
"--config",
|
73
|
+
help="Path to a INI config file.",
|
74
|
+
env_var="SLIDGE_CONFIG",
|
75
|
+
is_config_file=True,
|
76
|
+
)
|
77
|
+
p.add_argument(
|
78
|
+
"-q",
|
79
|
+
"--quiet",
|
80
|
+
help="loglevel=WARNING",
|
81
|
+
action="store_const",
|
82
|
+
dest="loglevel",
|
83
|
+
const=logging.WARNING,
|
84
|
+
default=logging.INFO,
|
85
|
+
env_var="SLIDGE_QUIET",
|
86
|
+
)
|
87
|
+
p.add_argument(
|
88
|
+
"-d",
|
89
|
+
"--debug",
|
90
|
+
help="loglevel=DEBUG",
|
91
|
+
action="store_const",
|
92
|
+
dest="loglevel",
|
93
|
+
const=logging.DEBUG,
|
94
|
+
env_var="SLIDGE_DEBUG",
|
95
|
+
)
|
96
|
+
p.add_argument(
|
97
|
+
"--version",
|
98
|
+
action="version",
|
99
|
+
version=f"%(prog)s {__version__}",
|
100
|
+
)
|
101
|
+
configurator = MainConfig(config, p)
|
102
|
+
return configurator
|
103
|
+
|
104
|
+
|
105
|
+
def get_parser():
|
106
|
+
return get_configurator().parser
|
107
|
+
|
108
|
+
|
109
|
+
def configure():
|
110
|
+
configurator = get_configurator()
|
111
|
+
args, unknown_argv = configurator.set_conf()
|
112
|
+
|
113
|
+
if not (h := config.HOME_DIR).exists():
|
114
|
+
logging.info("Creating directory '%s'", h)
|
115
|
+
h.mkdir()
|
116
|
+
|
117
|
+
db_file = config.HOME_DIR / "slidge.db"
|
118
|
+
user_store.set_file(db_file, args.secret_key)
|
119
|
+
|
120
|
+
config.UPLOAD_REQUESTER = config.UPLOAD_REQUESTER or config.JID.bare
|
121
|
+
|
122
|
+
return unknown_argv
|
123
|
+
|
124
|
+
|
125
|
+
def handle_sigterm(_signum, _frame):
|
126
|
+
logging.info("Caught SIGTERM")
|
127
|
+
raise SigTermInterrupt
|
128
|
+
|
129
|
+
|
130
|
+
def main():
|
131
|
+
signal.signal(signal.SIGTERM, handle_sigterm)
|
132
|
+
|
133
|
+
unknown_argv = configure()
|
134
|
+
logging.info("Starting slidge version %s", __version__)
|
135
|
+
|
136
|
+
legacy_module = importlib.import_module(config.LEGACY_MODULE)
|
137
|
+
logging.debug("Legacy module: %s", dir(legacy_module))
|
138
|
+
logging.info(
|
139
|
+
"Starting legacy module: '%s' version %s",
|
140
|
+
config.LEGACY_MODULE,
|
141
|
+
getattr(legacy_module, "__version__", "No version"),
|
142
|
+
)
|
143
|
+
|
144
|
+
if plugin_config_obj := getattr(
|
145
|
+
legacy_module, "config", getattr(legacy_module, "Config", None)
|
146
|
+
):
|
147
|
+
logging.debug("Found a config object in plugin: %r", plugin_config_obj)
|
148
|
+
ConfigModule.ENV_VAR_PREFIX += (
|
149
|
+
f"_{config.LEGACY_MODULE.split('.')[-1].upper()}_"
|
150
|
+
)
|
151
|
+
logging.debug("Env var prefix: %s", ConfigModule.ENV_VAR_PREFIX)
|
152
|
+
ConfigModule(plugin_config_obj).set_conf(unknown_argv)
|
153
|
+
else:
|
154
|
+
if unknown_argv:
|
155
|
+
raise RuntimeError("Some arguments have not been recognized", unknown_argv)
|
156
|
+
|
157
|
+
migrate()
|
158
|
+
|
159
|
+
BaseGateway.store = SlidgeStore(get_engine(config.DB_URL))
|
160
|
+
gateway: BaseGateway = BaseGateway.get_unique_subclass()()
|
161
|
+
avatar_cache.http = gateway.http
|
162
|
+
avatar_cache.store = gateway.store.avatars
|
163
|
+
avatar_cache.set_dir(config.HOME_DIR / "slidge_avatars_v3")
|
164
|
+
avatar_cache.legacy_avatar_type = gateway.AVATAR_ID_TYPE
|
165
|
+
|
166
|
+
PepAvatar.store = gateway.store
|
167
|
+
PepNick.contact_store = gateway.store.contacts
|
168
|
+
|
169
|
+
gateway.connect()
|
170
|
+
|
171
|
+
return_code = 0
|
172
|
+
try:
|
173
|
+
gateway.loop.run_forever()
|
174
|
+
except KeyboardInterrupt:
|
175
|
+
logging.debug("Received SIGINT")
|
176
|
+
except SigTermInterrupt:
|
177
|
+
logging.debug("Received SIGTERM")
|
178
|
+
except SystemExit as e:
|
179
|
+
return_code = e.code # type: ignore
|
180
|
+
logging.debug("Exit called")
|
181
|
+
except Exception as e:
|
182
|
+
return_code = 2
|
183
|
+
logging.exception("Exception in __main__")
|
184
|
+
logging.exception(e)
|
185
|
+
finally:
|
186
|
+
if gateway.has_crashed:
|
187
|
+
if return_code != 0:
|
188
|
+
logging.warning("Return code has been set twice. Please report this.")
|
189
|
+
return_code = 3
|
190
|
+
if gateway.is_connected():
|
191
|
+
logging.debug("Gateway is connected, cleaning up")
|
192
|
+
gateway.loop.run_until_complete(asyncio.gather(*gateway.shutdown()))
|
193
|
+
gateway.disconnect()
|
194
|
+
gateway.loop.run_until_complete(gateway.disconnected)
|
195
|
+
else:
|
196
|
+
logging.debug("Gateway is not connected, no need to clean up")
|
197
|
+
avatar_cache.close()
|
198
|
+
gateway.loop.run_until_complete(gateway.http.close())
|
199
|
+
logging.info("Successful clean shut down")
|
200
|
+
logging.debug("Exiting with code %s", return_code)
|
201
|
+
exit(return_code)
|
slidge/migration.py
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
import logging
|
2
2
|
import shutil
|
3
|
+
import sys
|
4
|
+
from pathlib import Path
|
5
|
+
|
6
|
+
from alembic import command
|
7
|
+
from alembic.config import Config
|
3
8
|
|
4
9
|
from .core import config
|
5
10
|
|
@@ -11,8 +16,33 @@ def remove_avatar_cache_v1():
|
|
11
16
|
shutil.rmtree(old_dir)
|
12
17
|
|
13
18
|
|
19
|
+
def get_alembic_cfg() -> Config:
|
20
|
+
alembic_cfg = Config()
|
21
|
+
alembic_cfg.set_section_option(
|
22
|
+
"alembic",
|
23
|
+
"script_location",
|
24
|
+
str(Path(__file__).parent / "db" / "alembic"),
|
25
|
+
)
|
26
|
+
return alembic_cfg
|
27
|
+
|
28
|
+
|
14
29
|
def migrate():
|
15
30
|
remove_avatar_cache_v1()
|
31
|
+
command.upgrade(get_alembic_cfg(), "head")
|
32
|
+
|
33
|
+
|
34
|
+
def main():
|
35
|
+
"""
|
36
|
+
Updates the (dev) database in ./dev/slidge.sqlite and generates a revision
|
37
|
+
|
38
|
+
Usage: python -m slidge.migration "Revision message blah blah blah"
|
39
|
+
"""
|
40
|
+
alembic_cfg = get_alembic_cfg()
|
41
|
+
command.upgrade(alembic_cfg, "head")
|
42
|
+
command.revision(alembic_cfg, sys.argv[1], autogenerate=True)
|
16
43
|
|
17
44
|
|
18
45
|
log = logging.getLogger(__name__)
|
46
|
+
|
47
|
+
if __name__ == "__main__":
|
48
|
+
main()
|
slidge/slixfix/__init__.py
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
# This module contains patches for slixmpp; some have pending requests upstream
|
2
2
|
# and should be removed on the next slixmpp release.
|
3
|
-
|
4
|
-
|
3
|
+
import logging
|
4
|
+
from collections import defaultdict
|
5
5
|
|
6
6
|
import slixmpp.plugins
|
7
7
|
from slixmpp import Message
|
8
8
|
from slixmpp.plugins.xep_0050 import XEP_0050, Command
|
9
|
+
from slixmpp.plugins.xep_0356.privilege import _VALID_ACCESSES, XEP_0356
|
9
10
|
from slixmpp.xmlstream import StanzaBase
|
10
11
|
|
11
12
|
from . import ( # xep_0356,
|
@@ -22,6 +23,37 @@ from . import ( # xep_0356,
|
|
22
23
|
xep_0490,
|
23
24
|
)
|
24
25
|
|
26
|
+
# ruff: noqa: F401
|
27
|
+
|
28
|
+
|
29
|
+
# TODO: Remove me once https://codeberg.org/poezio/slixmpp/pulls/3541 makes it
|
30
|
+
# to a slixmpp release
|
31
|
+
def _handle_privilege(self, msg: StanzaBase):
|
32
|
+
"""
|
33
|
+
Called when the XMPP server advertise the component's privileges.
|
34
|
+
|
35
|
+
Stores the privileges in this instance's granted_privileges attribute (a dict)
|
36
|
+
and raises the privileges_advertised event
|
37
|
+
"""
|
38
|
+
permissions = self.granted_privileges[msg.get_from()]
|
39
|
+
for perm in msg["privilege"]["perms"]:
|
40
|
+
access = perm["access"]
|
41
|
+
if access == "iq":
|
42
|
+
if not perm.get_plugin("namespace", check=True):
|
43
|
+
permissions.iq = defaultdict(lambda: perm["type"])
|
44
|
+
else:
|
45
|
+
for ns in perm["namespaces"]:
|
46
|
+
permissions.iq[ns["ns"]] = ns["type"]
|
47
|
+
elif access in _VALID_ACCESSES:
|
48
|
+
setattr(permissions, access, perm["type"])
|
49
|
+
else:
|
50
|
+
log.warning("Received an invalid privileged access: %s", access)
|
51
|
+
log.debug("Privileges: %s", self.granted_privileges)
|
52
|
+
self.xmpp.event("privileges_advertised")
|
53
|
+
|
54
|
+
|
55
|
+
XEP_0356._handle_privilege = _handle_privilege
|
56
|
+
|
25
57
|
|
26
58
|
def session_bind(self, jid):
|
27
59
|
self.xmpp["xep_0030"].add_feature(Command.namespace)
|
@@ -66,3 +98,4 @@ slixmpp.plugins.PLUGINS.extend(
|
|
66
98
|
|
67
99
|
|
68
100
|
Message.reply = reply # type: ignore
|
101
|
+
log = logging.getLogger(__name__)
|
slidge/slixfix/roster.py
CHANGED
@@ -1,6 +1,11 @@
|
|
1
|
+
from typing import TYPE_CHECKING
|
2
|
+
|
1
3
|
from slixmpp import JID
|
2
4
|
|
3
|
-
from ..util.db import log
|
5
|
+
from ..util.db import log
|
6
|
+
|
7
|
+
if TYPE_CHECKING:
|
8
|
+
from .. import BaseGateway
|
4
9
|
|
5
10
|
|
6
11
|
class YesSet(set):
|
@@ -23,6 +28,9 @@ class RosterBackend:
|
|
23
28
|
This is rudimentary but the only sane way I could come up with so far.
|
24
29
|
"""
|
25
30
|
|
31
|
+
def __init__(self, xmpp: "BaseGateway"):
|
32
|
+
self.xmpp = xmpp
|
33
|
+
|
26
34
|
@staticmethod
|
27
35
|
def entries(_owner_jid, _default=None):
|
28
36
|
return YesSet()
|
@@ -31,10 +39,9 @@ class RosterBackend:
|
|
31
39
|
def save(_owner_jid, _jid, _item_state, _db_state):
|
32
40
|
pass
|
33
41
|
|
34
|
-
|
35
|
-
def load(_owner_jid, jid, _db_state):
|
42
|
+
def load(self, _owner_jid, jid, _db_state):
|
36
43
|
log.debug("Load %s", jid)
|
37
|
-
user =
|
44
|
+
user = self.xmpp.store.users.get(JID(jid))
|
38
45
|
log.debug("User %s", user)
|
39
46
|
if user is None:
|
40
47
|
return {
|
@@ -26,6 +26,7 @@ class VCard4Provider(BasePlugin):
|
|
26
26
|
|
27
27
|
def __init__(self, *a, **k):
|
28
28
|
super(VCard4Provider, self).__init__(*a, **k)
|
29
|
+
# TODO: store that in DB and not in RAM
|
29
30
|
self._vcards = dict[JidStr, StoredVCard]()
|
30
31
|
|
31
32
|
def plugin_init(self):
|
@@ -56,6 +57,8 @@ class VCard4Provider(BasePlugin):
|
|
56
57
|
if not hasattr(self.xmpp, "get_session_from_jid"):
|
57
58
|
return None
|
58
59
|
jid = JID(jid)
|
60
|
+
if not jid.local:
|
61
|
+
return None
|
59
62
|
requested_by = JID(requested_by)
|
60
63
|
session = self.xmpp.get_session_from_jid(requested_by)
|
61
64
|
if session is None:
|
slidge/util/archive_msg.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
from copy import copy
|
2
2
|
from datetime import datetime, timezone
|
3
3
|
from typing import Optional, Union
|
4
|
+
from uuid import uuid4
|
4
5
|
from xml.etree import ElementTree as ET
|
5
6
|
|
6
7
|
from slixmpp import Message
|
@@ -30,7 +31,7 @@ class HistoryMessage:
|
|
30
31
|
else:
|
31
32
|
from_db = False
|
32
33
|
|
33
|
-
self.id = stanza["stanza_id"]["id"]
|
34
|
+
self.id = stanza["stanza_id"]["id"] or uuid4().hex
|
34
35
|
self.when: datetime = (
|
35
36
|
when or stanza["delay"]["stamp"] or datetime.now(tz=timezone.utc)
|
36
37
|
)
|
slidge/util/db.py
CHANGED
@@ -15,8 +15,6 @@ from typing import Iterable, Optional, Union
|
|
15
15
|
from pickle_secure import Pickler, Unpickler
|
16
16
|
from slixmpp import JID, Iq, Message, Presence
|
17
17
|
|
18
|
-
from .sql import db
|
19
|
-
|
20
18
|
|
21
19
|
# noinspection PyUnresolvedReferences
|
22
20
|
class EncryptedShelf(shelve.DbfilenameShelf):
|
@@ -94,10 +92,6 @@ class GatewayUser:
|
|
94
92
|
# """
|
95
93
|
return self.registration_form.get(field, default)
|
96
94
|
|
97
|
-
def commit(self):
|
98
|
-
db.user_store(self)
|
99
|
-
user_store.commit(self)
|
100
|
-
|
101
95
|
|
102
96
|
class UserStore:
|
103
97
|
"""
|
@@ -126,8 +120,6 @@ class UserStore:
|
|
126
120
|
self._users = EncryptedShelf(filename, key=secret_key)
|
127
121
|
else:
|
128
122
|
self._users = shelve.open(str(filename))
|
129
|
-
for user in self._users.values():
|
130
|
-
db.user_store(user)
|
131
123
|
log.info("Registered users in the DB: %s", list(self._users.keys()))
|
132
124
|
|
133
125
|
def get_all(self) -> Iterable[GatewayUser]:
|
@@ -138,28 +130,8 @@ class UserStore:
|
|
138
130
|
"""
|
139
131
|
return self._users.values()
|
140
132
|
|
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
133
|
def commit(self, user: GatewayUser):
|
162
|
-
self._users[user.
|
134
|
+
self._users[user.jid.bare] = user
|
163
135
|
self._users.sync()
|
164
136
|
|
165
137
|
def get(self, _gateway_jid, _node, ifrom: JID, iq) -> Optional[GatewayUser]:
|
@@ -179,24 +151,6 @@ class UserStore:
|
|
179
151
|
log.debug("Getting user %s", ifrom.bare)
|
180
152
|
return self._users.get(ifrom.bare)
|
181
153
|
|
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
154
|
def get_by_jid(self, jid: JID) -> Optional[GatewayUser]:
|
201
155
|
"""
|
202
156
|
Convenience function to get a user from their JID.
|
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,17 @@ class SlidgeTest(SlixTestPlus):
|
|
209
215
|
self.plugin, LegacyBookmarks, base_ok=True
|
210
216
|
)
|
211
217
|
|
218
|
+
engine = self.db_engine = create_engine("sqlite+pysqlite:///:memory:")
|
219
|
+
Base.metadata.create_all(engine)
|
220
|
+
BaseGateway.store = SlidgeStore(engine)
|
212
221
|
self.xmpp = BaseGateway.get_self_or_unique_subclass()()
|
213
|
-
|
222
|
+
self.xmpp.TEST_MODE = True
|
223
|
+
PepNick.contact_store = self.xmpp.store.contacts
|
224
|
+
PepAvatar.store = self.xmpp.store
|
225
|
+
avatar_cache.store = self.xmpp.store.avatars
|
226
|
+
avatar_cache.set_dir(Path(tempfile.mkdtemp()))
|
214
227
|
self.xmpp._always_send_everything = True
|
228
|
+
engine.echo = True
|
215
229
|
|
216
230
|
self.xmpp.connection_made(TestTransport(self.xmpp))
|
217
231
|
self.xmpp.session_bind_event.set()
|
@@ -242,10 +256,63 @@ class SlidgeTest(SlixTestPlus):
|
|
242
256
|
self.xmpp.use_presence_ids = False
|
243
257
|
Error.namespace = "jabber:component:accept"
|
244
258
|
|
259
|
+
def tearDown(self):
|
260
|
+
self.db_engine.echo = False
|
261
|
+
super().tearDown()
|
262
|
+
import slidge.db.store
|
263
|
+
|
264
|
+
if slidge.db.store._session is not None:
|
265
|
+
slidge.db.store._session.commit()
|
266
|
+
slidge.db.store._session = None
|
267
|
+
Base.metadata.drop_all(self.xmpp.store._engine)
|
268
|
+
|
269
|
+
def setup_logged_session(self):
|
270
|
+
user = self.xmpp.store.users.new(
|
271
|
+
JID("romeo@montague.lit/gajim"), {"username": "romeo", "city": ""}
|
272
|
+
)
|
273
|
+
user.preferences = {"sync_avatar": True, "sync_presence": True}
|
274
|
+
self.xmpp.store.users.update(user)
|
275
|
+
|
276
|
+
with self.xmpp.store.session() as session:
|
277
|
+
session.execute(delete(Contact))
|
278
|
+
session.commit()
|
279
|
+
|
280
|
+
self.run_coro(self.xmpp._on_user_register(Iq(sfrom="romeo@montague.lit/gajim")))
|
281
|
+
welcome = self.next_sent()
|
282
|
+
assert welcome["body"]
|
283
|
+
stanza = self.next_sent()
|
284
|
+
assert "logging in" in stanza["status"].lower(), stanza
|
285
|
+
stanza = self.next_sent()
|
286
|
+
assert "syncing contacts" in stanza["status"].lower(), stanza
|
287
|
+
stanza = self.next_sent()
|
288
|
+
assert "yup" in stanza["status"].lower(), stanza
|
289
|
+
|
290
|
+
self.romeo = BaseSession.get_self_or_unique_subclass().from_jid(
|
291
|
+
JID("romeo@montague.lit")
|
292
|
+
)
|
293
|
+
self.juliet: LegacyContact = self.run_coro(
|
294
|
+
self.romeo.contacts.by_legacy_id("juliet")
|
295
|
+
)
|
296
|
+
self.room: LegacyMUC = self.run_coro(self.romeo.bookmarks.by_legacy_id("room"))
|
297
|
+
self.first_witch: LegacyParticipant = self.run_coro(
|
298
|
+
self.room.get_participant("firstwitch")
|
299
|
+
)
|
300
|
+
self.send( # language=XML
|
301
|
+
"""
|
302
|
+
<iq type="get"
|
303
|
+
to="romeo@montague.lit"
|
304
|
+
id="1"
|
305
|
+
from="aim.shakespeare.lit">
|
306
|
+
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
307
|
+
<items node="urn:xmpp:avatar:metadata" />
|
308
|
+
</pubsub>
|
309
|
+
</iq>
|
310
|
+
"""
|
311
|
+
)
|
312
|
+
|
245
313
|
@classmethod
|
246
314
|
def tearDownClass(cls):
|
247
315
|
reset_subclasses()
|
248
|
-
user_store._users = None
|
249
316
|
|
250
317
|
|
251
318
|
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:
|
@@ -84,13 +84,16 @@ class MessageReference(Generic[LegacyMessageType]):
|
|
84
84
|
|
85
85
|
At the very minimum, the legacy message ID attribute must be set, but to
|
86
86
|
ensure that the quote is displayed in all XMPP clients, the author must also
|
87
|
-
be set
|
87
|
+
be set (use the string "user" if the slidge user is the author of the referenced
|
88
|
+
message).
|
88
89
|
The body is used as a fallback for XMPP clients that do not support :xep:`0461`
|
89
90
|
of that failed to find the referenced message.
|
90
91
|
"""
|
91
92
|
|
92
93
|
legacy_id: LegacyMessageType
|
93
|
-
author: Optional[Union["
|
94
|
+
author: Optional[Union[Literal["user"], "LegacyParticipant", "LegacyContact"]] = (
|
95
|
+
None
|
96
|
+
)
|
94
97
|
body: Optional[str] = None
|
95
98
|
|
96
99
|
|
@@ -178,3 +181,25 @@ class Mention(NamedTuple):
|
|
178
181
|
class Hat(NamedTuple):
|
179
182
|
uri: str
|
180
183
|
title: str
|
184
|
+
|
185
|
+
|
186
|
+
class UserPreferences(TypedDict):
|
187
|
+
sync_avatar: bool
|
188
|
+
sync_presence: bool
|
189
|
+
|
190
|
+
|
191
|
+
class MamMetadata(NamedTuple):
|
192
|
+
id: str
|
193
|
+
sent_on: datetime
|
194
|
+
|
195
|
+
|
196
|
+
class HoleBound(NamedTuple):
|
197
|
+
id: int | str
|
198
|
+
timestamp: datetime
|
199
|
+
|
200
|
+
|
201
|
+
class CachedPresence(NamedTuple):
|
202
|
+
last_seen: Optional[datetime] = None
|
203
|
+
ptype: Optional[PresenceTypes] = None
|
204
|
+
pstatus: Optional[str] = None
|
205
|
+
pshow: Optional[PresenceShows] = None
|
slidge/util/util.py
CHANGED
@@ -4,7 +4,9 @@ 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
|
9
|
+
from time import time
|
8
10
|
from typing import TYPE_CHECKING, Callable, NamedTuple, Optional, Type
|
9
11
|
|
10
12
|
from .types import Mention, ResourceDict
|
@@ -293,3 +295,23 @@ def replace_mentions(
|
|
293
295
|
cursor = mention.end
|
294
296
|
pieces.append(text[cursor:])
|
295
297
|
return "".join(pieces)
|
298
|
+
|
299
|
+
|
300
|
+
def with_session(func):
|
301
|
+
@wraps(func)
|
302
|
+
async def wrapped(self, *args, **kwargs):
|
303
|
+
with self.xmpp.store.session():
|
304
|
+
return await func(self, *args, **kwargs)
|
305
|
+
|
306
|
+
return wrapped
|
307
|
+
|
308
|
+
|
309
|
+
def timeit(func):
|
310
|
+
@wraps(func)
|
311
|
+
async def wrapped(self, *args, **kwargs):
|
312
|
+
start = time()
|
313
|
+
r = await func(self, *args, **kwargs)
|
314
|
+
self.log.info("%s took %s ms", func.__name__, round((time() - start) * 1000))
|
315
|
+
return r
|
316
|
+
|
317
|
+
return wrapped
|