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/__init__.py CHANGED
@@ -7,6 +7,7 @@ Contains importable classes for a minimal function :term:`Legacy Module`.
7
7
  import sys
8
8
  import warnings
9
9
  from importlib.metadata import PackageNotFoundError, version
10
+ from typing import Any
10
11
 
11
12
  from . import slixfix # noqa: F401
12
13
  from .command import FormField, SearchResult # noqa: F401
@@ -31,11 +32,13 @@ def entrypoint(module_name: str) -> None:
31
32
  main_func(module_name)
32
33
 
33
34
 
34
- def formatwarning(message, category, filename, lineno, line=""):
35
+ def formatwarning(
36
+ message: Any, category: Any, filename: Any, lineno: Any, line: str = ""
37
+ ) -> str:
35
38
  return f"{filename}:{lineno}:{category.__name__}:{message}\n"
36
39
 
37
40
 
38
- warnings.formatwarning = formatwarning
41
+ warnings.formatwarning = formatwarning # type:ignore
39
42
 
40
43
  try:
41
44
  __version__ = version("slidge")
slidge/command/adhoc.py CHANGED
@@ -4,7 +4,7 @@ import logging
4
4
  from functools import partial
5
5
  from typing import TYPE_CHECKING, Any, Callable, Optional, Union
6
6
 
7
- from slixmpp import JID, Iq # type: ignore[attr-defined]
7
+ from slixmpp import JID, Iq
8
8
  from slixmpp.exceptions import XMPPError
9
9
  from slixmpp.plugins.xep_0004 import Form as SlixForm # type: ignore[attr-defined]
10
10
  from slixmpp.plugins.xep_0030.stanza.items import DiscoItems
@@ -258,6 +258,8 @@ class AdhocProvider:
258
258
  if not all_items:
259
259
  return DiscoItems()
260
260
 
261
+ session = self.xmpp.get_session_from_jid(ifrom)
262
+
261
263
  filtered_items = DiscoItems()
262
264
  filtered_items["node"] = self.xmpp.plugin["xep_0050"].stanza.Command.namespace
263
265
  for item in all_items:
@@ -265,7 +267,9 @@ class AdhocProvider:
265
267
  if item["node"] in self._categories:
266
268
  for command in self._categories[item["node"]]:
267
269
  try:
268
- command.raise_if_not_authorized(ifrom)
270
+ command.raise_if_not_authorized(
271
+ ifrom, fetch_session=False, session=session
272
+ )
269
273
  except XMPPError:
270
274
  authorized = False
271
275
  else:
@@ -273,7 +277,9 @@ class AdhocProvider:
273
277
  break
274
278
  else:
275
279
  try:
276
- self._commands[item["node"]].raise_if_not_authorized(ifrom)
280
+ self._commands[item["node"]].raise_if_not_authorized(
281
+ ifrom, fetch_session=False, session=session
282
+ )
277
283
  except XMPPError:
278
284
  authorized = False
279
285
 
slidge/command/admin.py CHANGED
@@ -9,6 +9,7 @@ from slixmpp import JID
9
9
  from slixmpp.exceptions import XMPPError
10
10
 
11
11
  from ..core import config
12
+ from ..db.models import GatewayUser
12
13
  from ..util.types import AnyBaseSession
13
14
  from .base import (
14
15
  NODE_PREFIX,
@@ -38,13 +39,14 @@ class ListUsers(AdminCommand):
38
39
 
39
40
  async def run(self, _session, _ifrom, *_):
40
41
  items = []
41
- for u in self.xmpp.store.users.get_all():
42
- d = u.registration_date
43
- if d is None:
44
- joined = ""
45
- else:
46
- joined = d.isoformat(timespec="seconds")
47
- items.append({"jid": u.jid.bare, "joined": joined})
42
+ with self.xmpp.store.session() as orm:
43
+ for u in orm.query(GatewayUser).all():
44
+ d = u.registration_date
45
+ if d is None:
46
+ joined = ""
47
+ else:
48
+ joined = d.isoformat(timespec="seconds")
49
+ items.append({"jid": u.jid.bare, "joined": joined})
48
50
  return TableResult(
49
51
  description="List of registered users",
50
52
  fields=[FormField("jid", type="jid-single"), FormField("joined")],
@@ -59,7 +61,7 @@ class SlidgeInfo(AdminCommand):
59
61
  NODE = NODE_PREFIX + CHAT_COMMAND
60
62
  ACCESS = CommandAccess.ANY
61
63
 
62
- async def run(self, _session, _ifrom, *_):
64
+ async def run(self, _session, _ifrom, *_) -> str:
63
65
  start = self.xmpp.datetime_started # type:ignore
64
66
  uptime = datetime.now() - start
65
67
 
@@ -125,7 +127,8 @@ class DeleteUser(AdminCommand):
125
127
  self, form_values: FormValues, _session: AnyBaseSession, _ifrom: JID
126
128
  ) -> Confirmation:
127
129
  jid: JID = form_values.get("jid") # type:ignore
128
- user = self.xmpp.store.users.get(jid)
130
+ with self.xmpp.store.session() as orm:
131
+ user = orm.query(GatewayUser).one_or_none()
129
132
  if user is None:
130
133
  raise XMPPError("item-not-found", text=f"There is no user '{jid}'")
131
134
 
@@ -138,7 +141,8 @@ class DeleteUser(AdminCommand):
138
141
  async def finish(
139
142
  self, _session: Optional[AnyBaseSession], _ifrom: JID, jid: JID
140
143
  ) -> None:
141
- user = self.xmpp.store.users.get(jid)
144
+ with self.xmpp.store.session() as orm:
145
+ user = orm.query(GatewayUser).one_or_none()
142
146
  if user is None:
143
147
  raise XMPPError("bad-request", f"{jid} has no account here!")
144
148
  await self.xmpp.unregister_user(user)
@@ -187,10 +191,10 @@ class Exec(AdminCommand):
187
191
 
188
192
  context = dict[str, Any]()
189
193
 
190
- def __init__(self, xmpp):
194
+ def __init__(self, xmpp) -> None:
191
195
  super().__init__(xmpp)
192
196
 
193
- async def run(self, session, ifrom: JID, *args):
197
+ async def run(self, session, ifrom: JID, *args) -> str:
194
198
  from contextlib import redirect_stdout
195
199
  from io import StringIO
196
200
 
slidge/command/base.py CHANGED
@@ -14,15 +14,14 @@ from typing import (
14
14
  Union,
15
15
  )
16
16
 
17
- from slixmpp import JID # type: ignore[attr-defined]
17
+ from slixmpp import JID
18
18
  from slixmpp.exceptions import XMPPError
19
19
  from slixmpp.plugins.xep_0004 import Form as SlixForm # type: ignore[attr-defined]
20
- from slixmpp.plugins.xep_0004 import (
21
- FormField as SlixFormField, # type: ignore[attr-defined]
22
- )
20
+ from slixmpp.plugins.xep_0004 import FormField as SlixFormField
23
21
  from slixmpp.types import JidStr
24
22
 
25
23
  from ..core import config
24
+ from ..db.models import GatewayUser
26
25
  from ..util.types import AnyBaseSession, FieldType
27
26
 
28
27
  NODE_PREFIX = "https://slidge.im/command/core/"
@@ -364,7 +363,7 @@ class Command(ABC):
364
363
 
365
364
  subclasses = list[Type["Command"]]()
366
365
 
367
- def __init__(self, xmpp: "BaseGateway"):
366
+ def __init__(self, xmpp: "BaseGateway") -> None:
368
367
  self.xmpp = xmpp
369
368
 
370
369
  def __init_subclass__(cls, **kwargs: Any) -> None:
@@ -388,28 +387,33 @@ class Command(ABC):
388
387
  raise XMPPError("feature-not-implemented")
389
388
 
390
389
  def _get_session(self, jid: JID) -> Optional["BaseSession[Any, Any]"]:
391
- user = self.xmpp.store.users.get(jid)
392
- if user is None:
393
- return None
394
-
395
- return self.xmpp.get_session_from_user(user)
390
+ return self.xmpp.get_session_from_jid(jid)
396
391
 
397
392
  def __can_use_command(self, jid: JID):
398
393
  j = jid.bare
399
394
  return self.xmpp.jid_validator.match(j) or j in config.ADMINS
400
395
 
401
- def raise_if_not_authorized(self, jid: JID) -> Optional["BaseSession[Any, Any]"]:
396
+ def raise_if_not_authorized(
397
+ self,
398
+ jid: JID,
399
+ fetch_session: bool = True,
400
+ session: Optional["BaseSession[Any, Any]"] = None,
401
+ ) -> Optional["BaseSession[Any, Any]"]:
402
402
  """
403
403
  Raise an appropriate error is jid is not authorized to use the command
404
404
 
405
405
  :param jid: jid of the entity trying to access the command
406
+ :param fetch_session:
407
+ :param session:
408
+
406
409
  :return:session of JID if it exists
407
410
  """
408
- session = self._get_session(jid)
409
411
  if not self.__can_use_command(jid):
410
412
  raise XMPPError(
411
413
  "bad-request", "Your JID is not allowed to use this gateway."
412
414
  )
415
+ if fetch_session:
416
+ session = self._get_session(jid)
413
417
 
414
418
  if self.ACCESS == CommandAccess.ADMIN_ONLY and not is_admin(jid):
415
419
  raise XMPPError("not-authorized")
@@ -5,7 +5,7 @@
5
5
  import asyncio
6
6
  import functools
7
7
  import logging
8
- from typing import TYPE_CHECKING, Callable, Literal, Optional, Union, overload
8
+ from typing import TYPE_CHECKING, Any, Callable, Literal, Optional, Union, overload
9
9
  from urllib.parse import quote as url_quote
10
10
 
11
11
  from slixmpp import JID, CoroutineCallback, Message, StanzaPath
@@ -22,7 +22,7 @@ if TYPE_CHECKING:
22
22
  class ChatCommandProvider:
23
23
  UNKNOWN = "Wut? I don't know that command: {}"
24
24
 
25
- def __init__(self, xmpp: "BaseGateway"):
25
+ def __init__(self, xmpp: "BaseGateway") -> None:
26
26
  self.xmpp = xmpp
27
27
  self._keywords = list[str]()
28
28
  self._commands: dict[str, Command] = {}
@@ -49,29 +49,36 @@ class ChatCommandProvider:
49
49
  raise RuntimeError("There is already a command triggered by '%s'", t)
50
50
  self._commands[t] = command
51
51
 
52
+ @overload
53
+ async def input(self, jid: JidStr, text: Optional[str] = None) -> str: ...
54
+
52
55
  @overload
53
56
  async def input(
54
- self, jid: JidStr, text: Optional[str], blocking: Literal[False]
57
+ self, jid: JidStr, text: Optional[str] = None, *, blocking: Literal[False] = ...
55
58
  ) -> asyncio.Future[str]: ...
56
59
 
57
60
  @overload
58
61
  async def input(
59
62
  self,
60
63
  jid: JidStr,
61
- text: Optional[str],
62
- mtype: MessageTypes = ...,
63
- blocking: Literal[True] = ...,
64
+ text: str | None = None,
65
+ *,
66
+ mtype: MessageTypes = "chat",
67
+ timeout: int = 60,
68
+ blocking: Literal[True] = True,
69
+ **msg_kwargs: Any,
64
70
  ) -> str: ...
65
71
 
66
72
  async def input(
67
73
  self,
68
- jid,
69
- text=None,
70
- mtype="chat",
71
- timeout=60,
72
- blocking=True,
73
- **msg_kwargs,
74
- ):
74
+ jid: JidStr,
75
+ text: str | None = None,
76
+ *,
77
+ mtype: MessageTypes = "chat",
78
+ timeout: int = 60,
79
+ blocking: bool = True,
80
+ **msg_kwargs: Any,
81
+ ) -> str | asyncio.Future[str]:
75
82
  """
76
83
  Request arbitrary user input using a simple chat message, and await the result.
77
84
 
@@ -269,7 +276,7 @@ class ChatCommandProvider:
269
276
  reply["body"] = f"Error: {e}"
270
277
  reply.send()
271
278
 
272
- def _handle_help(self, msg: Message, *rest):
279
+ def _handle_help(self, msg: Message, *rest) -> None:
273
280
  if len(rest) == 0:
274
281
  reply = msg.reply()
275
282
  reply["body"] = self._help(msg.get_from())
@@ -282,6 +289,8 @@ class ChatCommandProvider:
282
289
  self._not_found(msg, str(rest))
283
290
 
284
291
  def _help(self, mfrom: JID):
292
+ session = self.xmpp.get_session_from_jid(mfrom)
293
+
285
294
  msg = "Available commands:"
286
295
  for c in sorted(
287
296
  self._commands.values(),
@@ -299,7 +308,7 @@ class ChatCommandProvider:
299
308
  ),
300
309
  ):
301
310
  try:
302
- c.raise_if_not_authorized(mfrom)
311
+ c.raise_if_not_authorized(mfrom, fetch_session=False, session=session)
303
312
  except XMPPError:
304
313
  continue
305
314
  msg += f"\n{c.CHAT_COMMAND} -- {c.NAME}"
@@ -311,7 +320,7 @@ class ChatCommandProvider:
311
320
  raise XMPPError("item-not-found", e)
312
321
 
313
322
 
314
- def percent_encode(jid: JID):
323
+ def percent_encode(jid: JID) -> str:
315
324
  return f"{url_quote(jid.user)}@{jid.server}" # type:ignore
316
325
 
317
326
 
slidge/command/user.py CHANGED
@@ -2,7 +2,7 @@
2
2
  from copy import deepcopy
3
3
  from typing import TYPE_CHECKING, Any, Optional, Union, cast
4
4
 
5
- from slixmpp import JID # type:ignore[attr-defined]
5
+ from slixmpp import JID
6
6
  from slixmpp.exceptions import XMPPError
7
7
 
8
8
  from ..group.room import LegacyMUC
@@ -254,7 +254,7 @@ class Preferences(Command):
254
254
  assert session is not None
255
255
  current = session.user.preferences
256
256
  for field in fields:
257
- field.value = current.get(field.var) # type:ignore
257
+ field.value = current.get(field.var, field.value) # type:ignore
258
258
  return Form(
259
259
  title="Preferences",
260
260
  instructions=self.HELP,
@@ -268,11 +268,12 @@ class Preferences(Command):
268
268
  assert session is not None
269
269
  user = session.user
270
270
  user.preferences.update(form_values) # type:ignore
271
- self.xmpp.store.users.update(user)
272
271
  if form_values["sync_avatar"]:
273
272
  await self.xmpp.fetch_user_avatar(session)
274
273
  else:
275
- session.xmpp.store.users.set_avatar_hash(session.user_pk, None)
274
+ user.avatar_hash = None
275
+
276
+ self.xmpp.store.users.update(user)
276
277
  return "Your preferences have been updated."
277
278
 
278
279
 
@@ -294,9 +295,7 @@ class Unregister(Command):
294
295
 
295
296
  async def unregister(self, session: Optional[AnyBaseSession], _ifrom: JID) -> str:
296
297
  assert session is not None
297
- user = self.xmpp.store.users.get(session.user_jid)
298
- assert user is not None
299
- await self.xmpp.unregister_user(user)
298
+ await self.xmpp.unregister_user(session.user)
300
299
  return "You are not registered anymore. Bye!"
301
300
 
302
301
 
@@ -343,6 +342,6 @@ class LeaveGroup(Command):
343
342
  )
344
343
 
345
344
  @staticmethod
346
- async def finish(session: AnyBaseSession, _ifrom, group: LegacyMUC):
345
+ async def finish(session: AnyBaseSession, _ifrom, group: LegacyMUC) -> None:
347
346
  await session.on_leave_group(group.legacy_id)
348
347
  await session.bookmarks.remove(group, reason="You left this group via slidge.")