slidge 0.1.2__py3-none-any.whl → 0.2.0a0__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.
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 +109 -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 +81 -29
  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.2.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.2.dist-info/RECORD +0 -96
  61. {slidge-0.1.2.dist-info → slidge-0.2.0a0.dist-info}/LICENSE +0 -0
  62. {slidge-0.1.2.dist-info → slidge-0.2.0a0.dist-info}/WHEEL +0 -0
  63. {slidge-0.1.2.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.2
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