slidge 0.2.12__py3-none-any.whl → 0.3.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 (77) hide show
  1. slidge/__init__.py +5 -2
  2. slidge/command/adhoc.py +9 -3
  3. slidge/command/admin.py +16 -12
  4. slidge/command/base.py +16 -12
  5. slidge/command/chat_command.py +25 -16
  6. slidge/command/user.py +7 -8
  7. slidge/contact/contact.py +119 -209
  8. slidge/contact/roster.py +106 -105
  9. slidge/core/config.py +2 -43
  10. slidge/core/dispatcher/caps.py +9 -2
  11. slidge/core/dispatcher/disco.py +13 -3
  12. slidge/core/dispatcher/message/__init__.py +1 -1
  13. slidge/core/dispatcher/message/chat_state.py +17 -8
  14. slidge/core/dispatcher/message/marker.py +7 -5
  15. slidge/core/dispatcher/message/message.py +117 -92
  16. slidge/core/dispatcher/muc/__init__.py +1 -1
  17. slidge/core/dispatcher/muc/admin.py +4 -4
  18. slidge/core/dispatcher/muc/mam.py +10 -6
  19. slidge/core/dispatcher/muc/misc.py +4 -2
  20. slidge/core/dispatcher/muc/owner.py +5 -3
  21. slidge/core/dispatcher/muc/ping.py +3 -1
  22. slidge/core/dispatcher/presence.py +21 -15
  23. slidge/core/dispatcher/registration.py +20 -12
  24. slidge/core/dispatcher/search.py +7 -3
  25. slidge/core/dispatcher/session_dispatcher.py +13 -5
  26. slidge/core/dispatcher/util.py +37 -27
  27. slidge/core/dispatcher/vcard.py +7 -4
  28. slidge/core/gateway.py +168 -84
  29. slidge/core/mixins/__init__.py +1 -11
  30. slidge/core/mixins/attachment.py +163 -148
  31. slidge/core/mixins/avatar.py +100 -177
  32. slidge/core/mixins/db.py +50 -2
  33. slidge/core/mixins/message.py +19 -17
  34. slidge/core/mixins/message_maker.py +29 -15
  35. slidge/core/mixins/message_text.py +38 -30
  36. slidge/core/mixins/presence.py +91 -35
  37. slidge/core/pubsub.py +42 -47
  38. slidge/core/session.py +88 -57
  39. slidge/db/alembic/versions/0337c90c0b96_unify_legacy_xmpp_id_mappings.py +183 -0
  40. slidge/db/alembic/versions/4dbd23a3f868_new_avatar_store.py +56 -0
  41. slidge/db/alembic/versions/54ce3cde350c_use_hash_for_avatar_filenames.py +50 -0
  42. slidge/db/alembic/versions/58b98dacf819_refactor.py +118 -0
  43. slidge/db/alembic/versions/75a62b74b239_ditch_hats_table.py +74 -0
  44. slidge/db/avatar.py +150 -119
  45. slidge/db/meta.py +33 -22
  46. slidge/db/models.py +68 -117
  47. slidge/db/store.py +412 -1094
  48. slidge/group/archive.py +61 -54
  49. slidge/group/bookmarks.py +74 -55
  50. slidge/group/participant.py +135 -142
  51. slidge/group/room.py +315 -312
  52. slidge/main.py +28 -18
  53. slidge/migration.py +2 -12
  54. slidge/slixfix/__init__.py +20 -4
  55. slidge/slixfix/delivery_receipt.py +6 -4
  56. slidge/slixfix/link_preview/link_preview.py +1 -1
  57. slidge/slixfix/link_preview/stanza.py +1 -1
  58. slidge/slixfix/roster.py +5 -7
  59. slidge/slixfix/xep_0077/register.py +8 -8
  60. slidge/slixfix/xep_0077/stanza.py +7 -7
  61. slidge/slixfix/xep_0100/gateway.py +12 -13
  62. slidge/slixfix/xep_0153/vcard_avatar.py +1 -1
  63. slidge/slixfix/xep_0292/vcard4.py +1 -1
  64. slidge/util/archive_msg.py +11 -5
  65. slidge/util/conf.py +23 -20
  66. slidge/util/jid_escaping.py +1 -1
  67. slidge/{core/mixins → util}/lock.py +6 -6
  68. slidge/util/test.py +30 -29
  69. slidge/util/types.py +22 -18
  70. slidge/util/util.py +19 -22
  71. {slidge-0.2.12.dist-info → slidge-0.3.0a0.dist-info}/METADATA +1 -1
  72. slidge-0.3.0a0.dist-info/RECORD +117 -0
  73. {slidge-0.2.12.dist-info → slidge-0.3.0a0.dist-info}/WHEEL +1 -1
  74. slidge-0.2.12.dist-info/RECORD +0 -112
  75. {slidge-0.2.12.dist-info → slidge-0.3.0a0.dist-info}/entry_points.txt +0 -0
  76. {slidge-0.2.12.dist-info → slidge-0.3.0a0.dist-info}/licenses/LICENSE +0 -0
  77. {slidge-0.2.12.dist-info → slidge-0.3.0a0.dist-info}/top_level.txt +0 -0
slidge/main.py CHANGED
@@ -17,6 +17,7 @@ import asyncio
17
17
  import importlib
18
18
  import inspect
19
19
  import logging
20
+ import logging.config
20
21
  import os
21
22
  import re
22
23
  import signal
@@ -27,7 +28,6 @@ import configargparse
27
28
  import slidge
28
29
  from slidge import BaseGateway
29
30
  from slidge.core import config
30
- from slidge.core.pubsub import PepAvatar, PepNick
31
31
  from slidge.db import SlidgeStore
32
32
  from slidge.db.avatar import avatar_cache
33
33
  from slidge.db.meta import get_engine
@@ -36,15 +36,18 @@ from slidge.util.conf import ConfigModule
36
36
 
37
37
 
38
38
  class MainConfig(ConfigModule):
39
- def update_dynamic_defaults(self, args):
39
+ def update_dynamic_defaults(self, args: configargparse.Namespace) -> None:
40
40
  # force=True is needed in case we call a logger before this is reached,
41
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
- )
42
+ if args.log_config:
43
+ logging.config.fileConfig(args.log_config)
44
+ else:
45
+ logging.basicConfig(
46
+ level=args.loglevel,
47
+ filename=args.log_file,
48
+ force=True,
49
+ format=args.log_format,
50
+ )
48
51
 
49
52
  if args.home_dir is None:
50
53
  args.home_dir = Path("/var/lib/slidge") / str(args.jid)
@@ -60,7 +63,7 @@ class SigTermInterrupt(Exception):
60
63
  pass
61
64
 
62
65
 
63
- def get_configurator(from_entrypoint: bool = False):
66
+ def get_configurator(from_entrypoint: bool = False) -> MainConfig:
64
67
  p = configargparse.ArgumentParser(
65
68
  default_config_files=os.getenv(
66
69
  "SLIDGE_CONF_DIR", "/etc/slidge/conf.d/*.conf"
@@ -74,10 +77,16 @@ def get_configurator(from_entrypoint: bool = False):
74
77
  env_var="SLIDGE_CONFIG",
75
78
  is_config_file=True,
76
79
  )
80
+ p.add_argument(
81
+ "--log-config",
82
+ help="Path to a INI config file to personalise logging output. Refer to "
83
+ "<https://docs.python.org/3/library/logging.config.html#configuration-file-format> "
84
+ "for details.",
85
+ )
77
86
  p.add_argument(
78
87
  "-q",
79
88
  "--quiet",
80
- help="loglevel=WARNING",
89
+ help="loglevel=WARNING (unused if --log-config is specified)",
81
90
  action="store_const",
82
91
  dest="loglevel",
83
92
  const=logging.WARNING,
@@ -87,7 +96,7 @@ def get_configurator(from_entrypoint: bool = False):
87
96
  p.add_argument(
88
97
  "-d",
89
98
  "--debug",
90
- help="loglevel=DEBUG",
99
+ help="loglevel=DEBUG (unused if --log-config is specified)",
91
100
  action="store_const",
92
101
  dest="loglevel",
93
102
  const=logging.DEBUG,
@@ -104,11 +113,11 @@ def get_configurator(from_entrypoint: bool = False):
104
113
  return configurator
105
114
 
106
115
 
107
- def get_parser():
116
+ def get_parser() -> configargparse.ArgumentParser:
108
117
  return get_configurator().parser
109
118
 
110
119
 
111
- def configure(from_entrypoint: bool):
120
+ def configure(from_entrypoint: bool) -> list[str]:
112
121
  configurator = get_configurator(from_entrypoint)
113
122
  args, unknown_argv = configurator.set_conf()
114
123
 
@@ -121,7 +130,7 @@ def configure(from_entrypoint: bool):
121
130
  return unknown_argv
122
131
 
123
132
 
124
- def handle_sigterm(_signum, _frame):
133
+ def handle_sigterm(_signum: int, _frame) -> None:
125
134
  logging.info("Caught SIGTERM")
126
135
  raise SigTermInterrupt
127
136
 
@@ -164,15 +173,16 @@ def main(module_name: str | None = None) -> None:
164
173
 
165
174
  migrate()
166
175
 
167
- store = SlidgeStore(get_engine(config.DB_URL))
176
+ store = SlidgeStore(
177
+ get_engine(
178
+ config.DB_URL, echo=logging.getLogger().isEnabledFor(level=logging.DEBUG)
179
+ )
180
+ )
168
181
  BaseGateway.store = store
169
182
  gateway: BaseGateway = BaseGateway.get_unique_subclass()()
170
183
  avatar_cache.store = gateway.store.avatars
171
184
  avatar_cache.set_dir(config.HOME_DIR / "slidge_avatars_v3")
172
185
 
173
- PepAvatar.store = gateway.store
174
- PepNick.contact_store = gateway.store.contacts
175
-
176
186
  gateway.connect()
177
187
 
178
188
  return_code = 0
slidge/migration.py CHANGED
@@ -13,7 +13,7 @@ from .db.models import GatewayUser
13
13
  from .db.store import SlidgeStore
14
14
 
15
15
 
16
- def remove_avatar_cache_v1():
16
+ def remove_avatar_cache_v1() -> None:
17
17
  old_dir = config.HOME_DIR / "slidge_avatars"
18
18
  if old_dir.exists():
19
19
  log.info("Avatar cache dir v1 found, clearing it.")
@@ -30,22 +30,12 @@ def get_alembic_cfg() -> Config:
30
30
  return alembic_cfg
31
31
 
32
32
 
33
- def remove_resource_parts_from_users() -> None:
34
- with SlidgeStore(get_engine(config.DB_URL)).session() as orm:
35
- for user in orm.query(GatewayUser).all():
36
- if user.jid.resource:
37
- user.jid = JID(user.jid.bare)
38
- orm.add(user)
39
- orm.commit()
40
-
41
-
42
33
  def migrate() -> None:
43
34
  remove_avatar_cache_v1()
44
35
  command.upgrade(get_alembic_cfg(), "head")
45
- remove_resource_parts_from_users()
46
36
 
47
37
 
48
- def main():
38
+ def main() -> None:
49
39
  """
50
40
  Updates the (dev) database in ./dev/slidge.sqlite and generates a revision
51
41
 
@@ -7,11 +7,13 @@ import uuid
7
7
 
8
8
  import slixmpp.plugins
9
9
  import slixmpp.stanza.roster
10
- from slixmpp import Message
10
+ from slixmpp import Message, register_stanza_plugin
11
11
  from slixmpp.exceptions import IqError
12
12
  from slixmpp.plugins.xep_0050 import XEP_0050, Command
13
13
  from slixmpp.plugins.xep_0356.permissions import IqPermission
14
14
  from slixmpp.plugins.xep_0356.privilege import XEP_0356
15
+ from slixmpp.plugins.xep_0385.sims import XEP_0385
16
+ from slixmpp.plugins.xep_0385.sims import stanza as stanza_sims
15
17
  from slixmpp.plugins.xep_0469.stanza import NS as PINNED_NS
16
18
  from slixmpp.plugins.xep_0469.stanza import Pinned
17
19
  from slixmpp.xmlstream import StanzaBase
@@ -25,7 +27,21 @@ from . import (
25
27
  )
26
28
 
27
29
 
28
- def set_pinned(self, val: bool):
30
+ def plugin_init(self):
31
+ register_stanza_plugin(self.xmpp["xep_0372"].stanza.Reference, stanza_sims.Sims)
32
+ register_stanza_plugin(Message, stanza_sims.Sims)
33
+
34
+ register_stanza_plugin(stanza_sims.Sims, stanza_sims.Sources)
35
+ register_stanza_plugin(stanza_sims.Sims, self.xmpp["xep_0234"].stanza.File)
36
+ register_stanza_plugin(
37
+ stanza_sims.Sources, self.xmpp["xep_0372"].stanza.Reference, iterable=True
38
+ )
39
+
40
+
41
+ XEP_0385.plugin_init = plugin_init
42
+
43
+
44
+ def set_pinned(self, val: bool) -> None:
29
45
  extensions = self.parent()
30
46
  if val:
31
47
  extensions.enable("pinned")
@@ -36,7 +52,7 @@ def set_pinned(self, val: bool):
36
52
  Pinned.set_pinned = set_pinned
37
53
 
38
54
 
39
- def session_bind(self, jid):
55
+ def session_bind(self, jid) -> None:
40
56
  self.xmpp["xep_0030"].add_feature(Command.namespace)
41
57
  # awful hack to for the disco items: we need to comment this line
42
58
  # related issue: https://todo.sr.ht/~nicoco/slidge/131
@@ -46,7 +62,7 @@ def session_bind(self, jid):
46
62
  XEP_0050.session_bind = session_bind # type:ignore
47
63
 
48
64
 
49
- def reply(self, body=None, clear=True):
65
+ def reply(self, body=None, clear: bool = True):
50
66
  """
51
67
  Overrides slixmpp's Message.reply(), since it strips to sender's resource
52
68
  for mtype=groupchat, and we do not want that, because when we raise an XMPPError,
@@ -15,10 +15,10 @@ if TYPE_CHECKING:
15
15
 
16
16
 
17
17
  class DeliveryReceipt:
18
- def __init__(self, xmpp: "BaseGateway"):
18
+ def __init__(self, xmpp: "BaseGateway") -> None:
19
19
  self.xmpp = xmpp
20
20
 
21
- def ack(self, msg: Message):
21
+ def ack(self, msg: Message) -> None:
22
22
  """
23
23
  Send a XEP-0184 (delivery receipt) in response to a message,
24
24
  if appropriate.
@@ -30,7 +30,9 @@ class DeliveryReceipt:
30
30
  ack = self.make_ack(msg["id"], msg["to"], msg["from"].bare, msg["type"])
31
31
  ack.send()
32
32
 
33
- def make_ack(self, msg_id: str, mfrom: JID, mto: JID, mtype: MessageTypes = "chat"):
33
+ def make_ack(
34
+ self, msg_id: str, mfrom: JID, mto: JID, mtype: MessageTypes = "chat"
35
+ ) -> Message:
34
36
  ack = self.xmpp.Message()
35
37
  ack["type"] = mtype
36
38
  ack["to"] = mto
@@ -38,7 +40,7 @@ class DeliveryReceipt:
38
40
  ack["receipt"] = msg_id
39
41
  return ack
40
42
 
41
- def requires_receipt(self, msg: Message):
43
+ def requires_receipt(self, msg: Message) -> bool:
42
44
  """
43
45
  Check if a message is eligible for a delivery receipt.
44
46
 
@@ -13,5 +13,5 @@ class LinkPreview(BasePlugin):
13
13
  dependencies = set()
14
14
  stanza = stanza
15
15
 
16
- def plugin_init(self):
16
+ def plugin_init(self) -> None:
17
17
  stanza.register_plugin()
@@ -93,7 +93,7 @@ class SiteName(OpenGraphMixin):
93
93
  name = plugin_attrib = "site_name"
94
94
 
95
95
 
96
- def register_plugin():
96
+ def register_plugin() -> None:
97
97
  for plugin in Title, Description, Url, Image, Type_, SiteName:
98
98
  register_stanza_plugin(plugin, Title)
99
99
  register_stanza_plugin(Message, LinkPreview, iterable=True)
slidge/slixfix/roster.py CHANGED
@@ -12,7 +12,7 @@ class YesSet(set):
12
12
  A pseudo-set which always test True for membership
13
13
  """
14
14
 
15
- def __contains__(self, item):
15
+ def __contains__(self, item) -> bool:
16
16
  log.debug("Test in")
17
17
  return True
18
18
 
@@ -27,7 +27,7 @@ class RosterBackend:
27
27
  This is rudimentary but the only sane way I could come up with so far.
28
28
  """
29
29
 
30
- def __init__(self, xmpp: "BaseGateway"):
30
+ def __init__(self, xmpp: "BaseGateway") -> None:
31
31
  self.xmpp = xmpp
32
32
 
33
33
  @staticmethod
@@ -35,14 +35,12 @@ class RosterBackend:
35
35
  return YesSet()
36
36
 
37
37
  @staticmethod
38
- def save(_owner_jid, _jid, _item_state, _db_state):
38
+ def save(_owner_jid, _jid, _item_state, _db_state) -> None:
39
39
  pass
40
40
 
41
41
  def load(self, _owner_jid, jid, _db_state):
42
- log.debug("Load %s", jid)
43
- user = self.xmpp.store.users.get(JID(jid))
44
- log.debug("User %s", user)
45
- if user is None:
42
+ session = self.xmpp.get_session_from_jid(JID(jid))
43
+ if session is None:
46
44
  return {
47
45
  "name": "",
48
46
  "groups": [],
@@ -71,7 +71,7 @@ class XEP_0077(BasePlugin):
71
71
  }
72
72
  _user_store: dict[str, dict[str, str]]
73
73
 
74
- def plugin_init(self):
74
+ def plugin_init(self) -> None:
75
75
  register_stanza_plugin(StreamFeatures, RegisterFeature)
76
76
  register_stanza_plugin(Iq, Register)
77
77
 
@@ -103,7 +103,7 @@ class XEP_0077(BasePlugin):
103
103
 
104
104
  self.xmpp.add_event_handler("connected", self._force_registration)
105
105
 
106
- def plugin_end(self):
106
+ def plugin_end(self) -> None:
107
107
  if not self.xmpp.is_component:
108
108
  self.xmpp.unregister_feature("register", self.order)
109
109
 
@@ -136,12 +136,12 @@ class XEP_0077(BasePlugin):
136
136
  reply.set_payload(reg.xml)
137
137
  return reply
138
138
 
139
- def _user_validate(self, _jid, _node, ifrom, registration):
139
+ def _user_validate(self, _jid, _node, ifrom, registration) -> None:
140
140
  self._user_store[ifrom.bare] = {
141
141
  key: registration[key] for key in self.form_fields
142
142
  }
143
143
 
144
- def _user_modify(self, _jid, _node, ifrom, registration):
144
+ def _user_modify(self, _jid, _node, ifrom, registration) -> None:
145
145
  self._user_store[ifrom.bare] = {
146
146
  key: registration[key] for key in self.form_fields
147
147
  }
@@ -212,11 +212,11 @@ class XEP_0077(BasePlugin):
212
212
  else:
213
213
  self.xmpp.event("user_modify", iq)
214
214
 
215
- async def _send_form(self, iq):
215
+ async def _send_form(self, iq) -> None:
216
216
  reply = await self.api["make_registration_form"](None, None, iq["from"], iq)
217
217
  reply.send()
218
218
 
219
- def _force_registration(self, _event):
219
+ def _force_registration(self, _event) -> None:
220
220
  if self.force_registration:
221
221
  self.xmpp.add_filter("in", self._force_stream_feature)
222
222
 
@@ -233,7 +233,7 @@ class XEP_0077(BasePlugin):
233
233
  self.xmpp.del_filter("in", self._force_stream_feature)
234
234
  return stanza_
235
235
 
236
- async def _handle_register_feature(self, _features):
236
+ async def _handle_register_feature(self, _features) -> bool:
237
237
  if "mechanisms" in self.xmpp.features:
238
238
  # We have already logged in with an account
239
239
  return False
@@ -276,7 +276,7 @@ class XEP_0077(BasePlugin):
276
276
  return iq.send(timeout=timeout, callback=callback)
277
277
 
278
278
 
279
- def _send_error(iq, code, error_type, name, text=""):
279
+ def _send_error(iq, code, error_type, name, text: str="") -> None:
280
280
  # It would be nice to raise XMPPError but the iq payload
281
281
  # should include the register info
282
282
  reply = iq.reply()
@@ -57,27 +57,27 @@ class Register(ElementBase):
57
57
  "key",
58
58
  }
59
59
 
60
- def get_registered(self):
60
+ def get_registered(self) -> bool:
61
61
  present = self.xml.find("{%s}registered" % self.namespace)
62
62
  return present is not None
63
63
 
64
- def get_remove(self):
64
+ def get_remove(self) -> bool:
65
65
  present = self.xml.find("{%s}remove" % self.namespace)
66
66
  return present is not None
67
67
 
68
- def set_registered(self, value):
68
+ def set_registered(self, value) -> None:
69
69
  if value:
70
70
  self.add_field("registered")
71
71
  else:
72
72
  del self["registered"]
73
73
 
74
- def set_remove(self, value):
74
+ def set_remove(self, value) -> None:
75
75
  if value:
76
76
  self.add_field("remove")
77
77
  else:
78
78
  del self["remove"]
79
79
 
80
- def add_field(self, value):
80
+ def add_field(self, value: str) -> None:
81
81
  self._set_sub_text(value, "", keep=True)
82
82
 
83
83
  def get_fields(self):
@@ -87,12 +87,12 @@ class Register(ElementBase):
87
87
  fields.add(field)
88
88
  return fields
89
89
 
90
- def set_fields(self, fields):
90
+ def set_fields(self, fields) -> None:
91
91
  del self["fields"]
92
92
  for field in fields:
93
93
  self._set_sub_text(field, "", keep=True)
94
94
 
95
- def del_fields(self):
95
+ def del_fields(self) -> None:
96
96
  for field in self.form_fields:
97
97
  self._del_sub(field)
98
98
 
@@ -27,7 +27,7 @@ class XEP_0100(BasePlugin):
27
27
  "needs_registration": True,
28
28
  }
29
29
 
30
- def plugin_init(self):
30
+ def plugin_init(self) -> None:
31
31
  if not self.xmpp.is_component:
32
32
  log.error("Only components can be gateways, aborting plugin load")
33
33
  return
@@ -50,7 +50,7 @@ class XEP_0100(BasePlugin):
50
50
 
51
51
  register_stanza_plugin(Iq, stanza.Gateway)
52
52
 
53
- def plugin_end(self):
53
+ def plugin_end(self) -> None:
54
54
  if not self.xmpp.is_component:
55
55
  self.xmpp.remove_event_handler("user_register", self.on_user_register)
56
56
  self.xmpp.remove_event_handler("user_unregister", self.on_user_unregister)
@@ -63,18 +63,16 @@ class XEP_0100(BasePlugin):
63
63
  async def get_user(self, stanza):
64
64
  return await self.xmpp["xep_0077"].api["user_get"](None, None, None, stanza)
65
65
 
66
- async def on_user_unregister(self, iq: Iq):
66
+ async def on_user_unregister(self, iq: Iq) -> None:
67
67
  self.xmpp.send_presence(pto=iq.get_from().bare, ptype="unavailable")
68
68
  self.xmpp.send_presence(pto=iq.get_from().bare, ptype="unsubscribe")
69
69
  self.xmpp.send_presence(pto=iq.get_from().bare, ptype="unsubscribed")
70
70
 
71
- async def on_user_register(self, iq: Iq):
71
+ async def on_user_register(self, iq: Iq) -> None:
72
72
  self.xmpp.client_roster[iq.get_from()].load()
73
73
  await self.add_component_to_roster(jid=iq.get_from())
74
74
 
75
- async def add_component_to_roster(self, jid: JID):
76
- if config.NO_ROSTER_PUSH:
77
- return
75
+ async def add_component_to_roster(self, jid: JID) -> None:
78
76
  items = {
79
77
  self.xmpp.boundjid.bare: {
80
78
  "name": self.component_name,
@@ -85,17 +83,18 @@ class XEP_0100(BasePlugin):
85
83
  try:
86
84
  await self._set_roster(jid, items)
87
85
  except PermissionError:
86
+ from slidge import __version__
87
+
88
88
  warnings.warn(
89
- "Slidge does not have the privilege to manage users' rosters. "
90
- "Users should add the slidge component to their rosters manually."
89
+ "Slidge does not have the privilege to manage rosters. See "
90
+ f"https://slidge.im/docs/slidge/{__version__}/admin/privilege.html"
91
91
  )
92
- if config.ROSTER_PUSH_PRESENCE_SUBSCRIPTION_REQUEST_FALLBACK:
93
- self.xmpp.send_presence(ptype="subscribe", pto=jid.bare)
92
+ self.xmpp.send_presence(ptype="subscribe", pto=jid.bare)
94
93
 
95
- async def _set_roster(self, jid, items):
94
+ async def _set_roster(self, jid, items) -> None:
96
95
  await self.xmpp["xep_0356"].set_roster(jid=jid.bare, roster_items=items)
97
96
 
98
- def on_presence_unsubscribe(self, p: Presence):
97
+ def on_presence_unsubscribe(self, p: Presence) -> None:
99
98
  if p.get_to() == self.xmpp.boundjid.bare:
100
99
  log.debug("REMOVE: Our roster: %s", self.xmpp.client_roster)
101
100
  self.xmpp["xep_0077"].api["user_remove"](None, None, p["from"], p)
@@ -10,5 +10,5 @@ class XEP_0153(BasePlugin):
10
10
  dependencies = {"xep_0054"}
11
11
  stanza = stanza
12
12
 
13
- def plugin_init(self):
13
+ def plugin_init(self) -> None:
14
14
  register_stanza_plugin(Presence, VCardTempUpdate)
@@ -7,7 +7,7 @@ class VCard4Provider(BasePlugin):
7
7
  description = "VCard4 Provider"
8
8
  dependencies = {"xep_0030"}
9
9
 
10
- def plugin_init(self):
10
+ def plugin_init(self) -> None:
11
11
  self.xmpp.plugin["xep_0030"].add_feature(NS)
12
12
 
13
13
 
@@ -5,10 +5,14 @@ from uuid import uuid4
5
5
  from xml.etree import ElementTree as ET
6
6
 
7
7
  from slixmpp import Message
8
- from slixmpp.plugins.xep_0297 import Forwarded
8
+ from slixmpp.plugins.xep_0297.stanza import Forwarded
9
9
 
10
10
 
11
- def fix_namespaces(xml, old="{jabber:component:accept}", new="{jabber:client}"):
11
+ def fix_namespaces(
12
+ xml: ET.Element,
13
+ old: str = "{jabber:component:accept}",
14
+ new: str = "{jabber:client}",
15
+ ) -> None:
12
16
  """
13
17
  Hack to fix namespaces between jabber:component and jabber:client
14
18
 
@@ -24,7 +28,9 @@ def fix_namespaces(xml, old="{jabber:component:accept}", new="{jabber:client}"):
24
28
 
25
29
 
26
30
  class HistoryMessage:
27
- def __init__(self, stanza: Union[Message, str], when: Optional[datetime] = None):
31
+ def __init__(
32
+ self, stanza: Union[Message, str], when: Optional[datetime] = None
33
+ ) -> None:
28
34
  if isinstance(stanza, str):
29
35
  from_db = True
30
36
  stanza = Message(xml=ET.fromstring(stanza))
@@ -48,14 +54,14 @@ class HistoryMessage:
48
54
  self.stanza: Message = stanza
49
55
 
50
56
  @property
51
- def stanza_component_ns(self):
57
+ def stanza_component_ns(self) -> Message:
52
58
  stanza = copy(self.stanza)
53
59
  fix_namespaces(
54
60
  stanza.xml, old="{jabber:client}", new="{jabber:component:accept}"
55
61
  )
56
62
  return stanza
57
63
 
58
- def forwarded(self):
64
+ def forwarded(self) -> Forwarded:
59
65
  forwarded = Forwarded()
60
66
  forwarded["delay"]["stamp"] = self.when
61
67
  forwarded.append(self.stanza)
slidge/util/conf.py CHANGED
@@ -1,7 +1,7 @@
1
1
  import logging
2
2
  from functools import cached_property
3
3
  from types import GenericAlias
4
- from typing import Optional, Union, get_args, get_origin, get_type_hints
4
+ from typing import Any, Optional, Union, cast, get_args, get_origin, get_type_hints
5
5
 
6
6
  import configargparse
7
7
 
@@ -11,31 +11,31 @@ class Option:
11
11
  DYNAMIC_DEFAULT_SUFFIX = "__DYNAMIC_DEFAULT"
12
12
  SHORT_SUFFIX = "__SHORT"
13
13
 
14
- def __init__(self, parent: "ConfigModule", name: str):
14
+ def __init__(self, parent: "ConfigModule", name: str) -> None:
15
15
  self.parent = parent
16
16
  self.config_obj = parent.config_obj
17
17
  self.name = name
18
18
 
19
19
  @cached_property
20
- def doc(self):
21
- return getattr(self.config_obj, self.name + self.DOC_SUFFIX)
20
+ def doc(self) -> str:
21
+ return getattr(self.config_obj, self.name + self.DOC_SUFFIX) # type:ignore
22
22
 
23
23
  @cached_property
24
- def required(self):
24
+ def required(self) -> bool:
25
25
  return not hasattr(
26
26
  self.config_obj, self.name + self.DYNAMIC_DEFAULT_SUFFIX
27
27
  ) and not hasattr(self.config_obj, self.name)
28
28
 
29
29
  @cached_property
30
- def default(self):
30
+ def default(self) -> Any:
31
31
  return getattr(self.config_obj, self.name, None)
32
32
 
33
33
  @cached_property
34
- def short(self):
34
+ def short(self) -> str | None:
35
35
  return getattr(self.config_obj, self.name + self.SHORT_SUFFIX, None)
36
36
 
37
37
  @cached_property
38
- def nargs(self):
38
+ def nargs(self) -> str | int | None:
39
39
  type_ = get_type_hints(self.config_obj).get(self.name, type(self.default))
40
40
 
41
41
  if isinstance(type_, GenericAlias):
@@ -44,9 +44,10 @@ class Option:
44
44
  return "*"
45
45
  else:
46
46
  return len(args)
47
+ return None
47
48
 
48
49
  @cached_property
49
- def type(self):
50
+ def type(self) -> Any:
50
51
  type_ = get_type_hints(self.config_obj).get(self.name, type(self.default))
51
52
 
52
53
  if _is_optional(type_):
@@ -58,14 +59,14 @@ class Option:
58
59
  return type_
59
60
 
60
61
  @cached_property
61
- def names(self):
62
+ def names(self) -> list[str]:
62
63
  res = ["--" + self.name.lower().replace("_", "-")]
63
64
  if s := self.short:
64
65
  res.append("-" + s)
65
66
  return res
66
67
 
67
68
  @cached_property
68
- def kwargs(self):
69
+ def kwargs(self) -> dict[str, Any]:
69
70
  kwargs = dict(
70
71
  required=self.required,
71
72
  help=self.doc,
@@ -87,7 +88,7 @@ class Option:
87
88
  kwargs["nargs"] = n
88
89
  return kwargs
89
90
 
90
- def name_to_env_var(self):
91
+ def name_to_env_var(self) -> str:
91
92
  return self.parent.ENV_VAR_PREFIX + self.name
92
93
 
93
94
 
@@ -96,10 +97,10 @@ class ConfigModule:
96
97
 
97
98
  def __init__(
98
99
  self,
99
- config_obj,
100
+ config_obj: Any,
100
101
  parser: Optional[configargparse.ArgumentParser] = None,
101
102
  skip_options: tuple[str, ...] = (),
102
- ):
103
+ ) -> None:
103
104
  self.config_obj = config_obj
104
105
  if parser is None:
105
106
  parser = configargparse.ArgumentParser()
@@ -108,7 +109,7 @@ class ConfigModule:
108
109
  self.skip_options = skip_options
109
110
  self.add_options_to_parser(skip_options)
110
111
 
111
- def _list_options(self):
112
+ def _list_options(self) -> set[str]:
112
113
  return {
113
114
  o
114
115
  for o in (set(dir(self.config_obj)) | set(get_type_hints(self.config_obj)))
@@ -118,7 +119,9 @@ class ConfigModule:
118
119
  and o.lower() not in self.skip_options
119
120
  }
120
121
 
121
- def set_conf(self, argv: Optional[list[str]] = None):
122
+ def set_conf(
123
+ self, argv: Optional[list[str]] = None
124
+ ) -> tuple[configargparse.Namespace, list[str]]:
122
125
  if argv is not None:
123
126
  # this is ugly, but necessary because for plugin config, we used
124
127
  # remaining argv.
@@ -186,7 +189,7 @@ class ConfigModule:
186
189
  res.append(Option(self, opt))
187
190
  return res
188
191
 
189
- def add_options_to_parser(self, skip_options: tuple[str, ...]):
192
+ def add_options_to_parser(self, skip_options: tuple[str, ...]) -> None:
190
193
  skip_options = tuple(o.lower() for o in skip_options)
191
194
  p = self.parser
192
195
  for o in sorted(self.options, key=lambda x: (not x.required, x.name)):
@@ -194,11 +197,11 @@ class ConfigModule:
194
197
  continue
195
198
  p.add_argument(*o.names, **o.kwargs)
196
199
 
197
- def update_dynamic_defaults(self, args):
200
+ def update_dynamic_defaults(self, args: configargparse.Namespace) -> None:
198
201
  pass
199
202
 
200
203
 
201
- def _is_optional(t):
204
+ def _is_optional(t: Any) -> bool:
202
205
  if get_origin(t) is Union:
203
206
  args = get_args(t)
204
207
  if len(args) == 2 and isinstance(None, args[1]):
@@ -206,7 +209,7 @@ def _is_optional(t):
206
209
  return False
207
210
 
208
211
 
209
- def _argv_to_option_name(arg: str):
212
+ def _argv_to_option_name(arg: str) -> str:
210
213
  return arg.upper().removeprefix("--").replace("-", "_")
211
214
 
212
215