slidge 0.1.3__py3-none-any.whl → 0.2.0a0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. slidge/__init__.py +3 -5
  2. slidge/__main__.py +2 -196
  3. slidge/__version__.py +5 -0
  4. slidge/command/adhoc.py +8 -1
  5. slidge/command/admin.py +5 -6
  6. slidge/command/base.py +1 -2
  7. slidge/command/register.py +32 -16
  8. slidge/command/user.py +85 -5
  9. slidge/contact/contact.py +93 -31
  10. slidge/contact/roster.py +54 -39
  11. slidge/core/config.py +13 -7
  12. slidge/core/gateway/base.py +139 -34
  13. slidge/core/gateway/disco.py +2 -4
  14. slidge/core/gateway/mam.py +1 -4
  15. slidge/core/gateway/ping.py +2 -3
  16. slidge/core/gateway/presence.py +1 -1
  17. slidge/core/gateway/registration.py +32 -21
  18. slidge/core/gateway/search.py +3 -5
  19. slidge/core/gateway/session_dispatcher.py +100 -51
  20. slidge/core/gateway/vcard_temp.py +6 -4
  21. slidge/core/mixins/__init__.py +11 -1
  22. slidge/core/mixins/attachment.py +15 -10
  23. slidge/core/mixins/avatar.py +66 -18
  24. slidge/core/mixins/base.py +8 -2
  25. slidge/core/mixins/message.py +11 -7
  26. slidge/core/mixins/message_maker.py +17 -9
  27. slidge/core/mixins/presence.py +14 -4
  28. slidge/core/pubsub.py +54 -212
  29. slidge/core/session.py +65 -33
  30. slidge/db/__init__.py +4 -0
  31. slidge/db/alembic/env.py +64 -0
  32. slidge/db/alembic/script.py.mako +26 -0
  33. slidge/db/alembic/versions/09f27f098baa_add_missing_attributes_in_room.py +36 -0
  34. slidge/db/alembic/versions/29f5280c61aa_store_subject_setter_in_room.py +37 -0
  35. slidge/db/alembic/versions/8d2ced764698_rely_on_db_to_store_contacts_rooms_and_.py +133 -0
  36. slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +76 -0
  37. slidge/db/alembic/versions/b33993e87db3_move_everything_to_persistent_db.py +214 -0
  38. slidge/db/alembic/versions/e91195719c2c_store_users_avatars_persistently.py +26 -0
  39. slidge/db/avatar.py +224 -0
  40. slidge/db/meta.py +65 -0
  41. slidge/db/models.py +365 -0
  42. slidge/db/store.py +976 -0
  43. slidge/group/archive.py +13 -14
  44. slidge/group/bookmarks.py +59 -56
  45. slidge/group/participant.py +77 -25
  46. slidge/group/room.py +242 -142
  47. slidge/main.py +201 -0
  48. slidge/migration.py +30 -0
  49. slidge/slixfix/__init__.py +35 -2
  50. slidge/slixfix/roster.py +11 -4
  51. slidge/slixfix/xep_0292/vcard4.py +1 -0
  52. slidge/util/db.py +1 -47
  53. slidge/util/test.py +21 -4
  54. slidge/util/types.py +24 -4
  55. {slidge-0.1.3.dist-info → slidge-0.2.0a0.dist-info}/METADATA +3 -1
  56. slidge-0.2.0a0.dist-info/RECORD +108 -0
  57. slidge/core/cache.py +0 -183
  58. slidge/util/schema.sql +0 -126
  59. slidge/util/sql.py +0 -508
  60. slidge-0.1.3.dist-info/RECORD +0 -96
  61. {slidge-0.1.3.dist-info → slidge-0.2.0a0.dist-info}/LICENSE +0 -0
  62. {slidge-0.1.3.dist-info → slidge-0.2.0a0.dist-info}/WHEEL +0 -0
  63. {slidge-0.1.3.dist-info → slidge-0.2.0a0.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 signal
21
+ from pathlib import Path
22
+
23
+ import configargparse
24
+
25
+ from slidge import BaseGateway
26
+ from slidge.__version__ import __version__
27
+ from slidge.core import config
28
+ from slidge.core.pubsub import PepAvatar, PepNick
29
+ from slidge.db import SlidgeStore
30
+ from slidge.db.avatar import avatar_cache
31
+ from slidge.db.meta import get_engine
32
+ from slidge.migration import migrate
33
+ from slidge.util.conf import ConfigModule
34
+ from slidge.util.db import user_store
35
+
36
+
37
+ class MainConfig(ConfigModule):
38
+ def update_dynamic_defaults(self, args):
39
+ # force=True is needed in case we call a logger before this is reached,
40
+ # or basicConfig has no effect
41
+ logging.basicConfig(
42
+ level=args.loglevel,
43
+ filename=args.log_file,
44
+ force=True,
45
+ format=args.log_format,
46
+ )
47
+
48
+ if args.home_dir is None:
49
+ args.home_dir = Path("/var/lib/slidge") / str(args.jid)
50
+
51
+ if args.user_jid_validator is None:
52
+ args.user_jid_validator = ".*@" + args.server
53
+
54
+ if args.db_url is None:
55
+ args.db_url = f"sqlite:///{args.home_dir}/slidge.sqlite"
56
+
57
+
58
+ class SigTermInterrupt(Exception):
59
+ pass
60
+
61
+
62
+ def get_configurator():
63
+ p = configargparse.ArgumentParser(
64
+ default_config_files=os.getenv(
65
+ "SLIDGE_CONF_DIR", "/etc/slidge/conf.d/*.conf"
66
+ ).split(":"),
67
+ description=__doc__,
68
+ )
69
+ p.add_argument(
70
+ "-c",
71
+ "--config",
72
+ help="Path to a INI config file.",
73
+ env_var="SLIDGE_CONFIG",
74
+ is_config_file=True,
75
+ )
76
+ p.add_argument(
77
+ "-q",
78
+ "--quiet",
79
+ help="loglevel=WARNING",
80
+ action="store_const",
81
+ dest="loglevel",
82
+ const=logging.WARNING,
83
+ default=logging.INFO,
84
+ env_var="SLIDGE_QUIET",
85
+ )
86
+ p.add_argument(
87
+ "-d",
88
+ "--debug",
89
+ help="loglevel=DEBUG",
90
+ action="store_const",
91
+ dest="loglevel",
92
+ const=logging.DEBUG,
93
+ env_var="SLIDGE_DEBUG",
94
+ )
95
+ p.add_argument(
96
+ "--version",
97
+ action="version",
98
+ version=f"%(prog)s {__version__}",
99
+ )
100
+ configurator = MainConfig(config, p)
101
+ return configurator
102
+
103
+
104
+ def get_parser():
105
+ return get_configurator().parser
106
+
107
+
108
+ def configure():
109
+ configurator = get_configurator()
110
+ args, unknown_argv = configurator.set_conf()
111
+
112
+ if not (h := config.HOME_DIR).exists():
113
+ logging.info("Creating directory '%s'", h)
114
+ h.mkdir()
115
+
116
+ db_file = config.HOME_DIR / "slidge.db"
117
+ user_store.set_file(db_file, args.secret_key)
118
+
119
+ avatar_cache.set_dir(h / "slidge_avatars_v3")
120
+
121
+ config.UPLOAD_REQUESTER = config.UPLOAD_REQUESTER or config.JID.bare
122
+
123
+ return unknown_argv
124
+
125
+
126
+ def handle_sigterm(_signum, _frame):
127
+ logging.info("Caught SIGTERM")
128
+ raise SigTermInterrupt
129
+
130
+
131
+ def main():
132
+ signal.signal(signal.SIGTERM, handle_sigterm)
133
+
134
+ unknown_argv = configure()
135
+ logging.info("Starting slidge version %s", __version__)
136
+
137
+ legacy_module = importlib.import_module(config.LEGACY_MODULE)
138
+ logging.debug("Legacy module: %s", dir(legacy_module))
139
+ logging.info(
140
+ "Starting legacy module: '%s' version %s",
141
+ config.LEGACY_MODULE,
142
+ getattr(legacy_module, "__version__", "No version"),
143
+ )
144
+
145
+ if plugin_config_obj := getattr(
146
+ legacy_module, "config", getattr(legacy_module, "Config", None)
147
+ ):
148
+ logging.debug("Found a config object in plugin: %r", plugin_config_obj)
149
+ ConfigModule.ENV_VAR_PREFIX += (
150
+ f"_{config.LEGACY_MODULE.split('.')[-1].upper()}_"
151
+ )
152
+ logging.debug("Env var prefix: %s", ConfigModule.ENV_VAR_PREFIX)
153
+ ConfigModule(plugin_config_obj).set_conf(unknown_argv)
154
+ else:
155
+ if unknown_argv:
156
+ raise RuntimeError("Some arguments have not been recognized", unknown_argv)
157
+
158
+ migrate()
159
+
160
+ BaseGateway.store = SlidgeStore(get_engine(config.DB_URL))
161
+ gateway: BaseGateway = BaseGateway.get_unique_subclass()()
162
+ avatar_cache.http = gateway.http
163
+ avatar_cache.store = gateway.store.avatars
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()
@@ -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
- # ruff: noqa: F401
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, user_store
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
- @staticmethod
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 = user_store.get_by_jid(JID(jid))
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):
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.bare_jid] = 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
@@ -20,6 +20,7 @@ from slixmpp.stanza.error import Error
20
20
  from slixmpp.test import SlixTest, TestTransport
21
21
  from slixmpp.xmlstream import highlight, tostring
22
22
  from slixmpp.xmlstream.matcher import MatchIDSender
23
+ from sqlalchemy import create_engine
23
24
 
24
25
  from slidge import (
25
26
  BaseGateway,
@@ -29,12 +30,15 @@ from slidge import (
29
30
  LegacyMUC,
30
31
  LegacyParticipant,
31
32
  LegacyRoster,
32
- user_store,
33
33
  )
34
34
 
35
35
  from ..command import Command
36
36
  from ..core import config
37
37
  from ..core.config import _TimedeltaSeconds
38
+ from ..core.pubsub import PepAvatar, PepNick
39
+ from ..db import SlidgeStore
40
+ from ..db.avatar import avatar_cache
41
+ from ..db.meta import Base
38
42
 
39
43
 
40
44
  class SlixTestPlus(SlixTest):
@@ -187,10 +191,10 @@ class SlidgeTest(SlixTestPlus):
187
191
  no_roster_push = False
188
192
  upload_requester = None
189
193
  ignore_delay_threshold = _TimedeltaSeconds("300")
194
+ last_seen_fallback = True
190
195
 
191
196
  @classmethod
192
197
  def setUpClass(cls):
193
- user_store.set_file(Path(tempfile.mkdtemp()) / "test.db")
194
198
  for k, v in vars(cls.Config).items():
195
199
  setattr(config, k.upper(), v)
196
200
 
@@ -210,7 +214,12 @@ class SlidgeTest(SlixTestPlus):
210
214
  )
211
215
 
212
216
  self.xmpp = BaseGateway.get_self_or_unique_subclass()()
213
-
217
+ engine = create_engine("sqlite+pysqlite:///:memory:", echo=True)
218
+ Base.metadata.create_all(engine)
219
+ self.xmpp.store = SlidgeStore(engine)
220
+ PepNick.contact_store = self.xmpp.store.contacts
221
+ PepAvatar.store = self.xmpp.store
222
+ avatar_cache.store = self.xmpp.store.avatars
214
223
  self.xmpp._always_send_everything = True
215
224
 
216
225
  self.xmpp.connection_made(TestTransport(self.xmpp))
@@ -242,10 +251,18 @@ class SlidgeTest(SlixTestPlus):
242
251
  self.xmpp.use_presence_ids = False
243
252
  Error.namespace = "jabber:component:accept"
244
253
 
254
+ def tearDown(self):
255
+ super().tearDown()
256
+ import slidge.db.store
257
+
258
+ if slidge.db.store._session is not None:
259
+ slidge.db.store._session.commit()
260
+ slidge.db.store._session = None
261
+ Base.metadata.drop_all(self.xmpp.store._engine)
262
+
245
263
  @classmethod
246
264
  def tearDownClass(cls):
247
265
  reset_subclasses()
248
- user_store._users = None
249
266
 
250
267
 
251
268
  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["GatewayUser", "LegacyParticipant", "LegacyContact"]] = None
94
+ author: Optional[Union[Literal["user"], "LegacyParticipant", "LegacyContact"]] = (
95
+ None
96
+ )
94
97
  body: Optional[str] = None
95
98
 
96
99
 
@@ -178,3 +181,20 @@ 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 CachedPresence(NamedTuple):
197
+ last_seen: Optional[datetime] = None
198
+ ptype: Optional[PresenceTypes] = None
199
+ pstatus: Optional[str] = None
200
+ pshow: Optional[PresenceShows] = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: slidge
3
- Version: 0.1.3
3
+ Version: 0.2.0a0
4
4
  Summary: XMPP bridging framework
5
5
  Home-page: https://sr.ht/~nicoco/slidge/
6
6
  License: AGPL-3.0-or-later
@@ -15,11 +15,13 @@ Classifier: Programming Language :: Python :: 3.11
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: alembic (>=1.13.1,<2.0.0)
18
19
  Requires-Dist: blurhash-python (>=1.2.1,<2.0.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)
23
25
  Project-URL: Documentation, https://slidge.im/
24
26
  Project-URL: Repository, https://git.sr.ht/~nicoco/slidge/
25
27
  Description-Content-Type: text/markdown