slidge 0.2.4__py3-none-any.whl → 0.2.6__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
slidge/__version__.py CHANGED
@@ -2,4 +2,4 @@ from slidge.util.util import get_version # noqa: F401
2
2
 
3
3
  # this is modified before publish, but if someone cloned from the repo,
4
4
  # it can help
5
- __version__ = "v0.2.4"
5
+ __version__ = "v0.2.6"
slidge/command/adhoc.py CHANGED
@@ -240,6 +240,14 @@ class AdhocProvider:
240
240
  :param iq: the disco query IQ
241
241
  :return: commands accessible to the given JID will be listed
242
242
  """
243
+ if not self.xmpp.jid_validator.match(str(jid)) and jid not in config.ADMINS:
244
+ raise XMPPError(
245
+ "forbidden",
246
+ "You are not authorized to execute adhoc commands on this gateway. "
247
+ "If this is unexpected, ask your administrator to verify that "
248
+ "'user-jid-validator' is correctly set in slidge's configuration.",
249
+ )
250
+
243
251
  all_items = self.xmpp.plugin["xep_0030"].static.get_items(jid, node, None, None)
244
252
  log.debug("Static items: %r", all_items)
245
253
  if not all_items:
slidge/command/base.py CHANGED
@@ -293,12 +293,16 @@ class FormField:
293
293
  return value
294
294
 
295
295
  def __validate_list_multi(self, value: list[str]) -> Union[list[str], list[JID]]:
296
+ # COMPAT: all the "if v" and "if not v" are workarounds for https://codeberg.org/slidge/slidge/issues/43
297
+ # They should be reverted once the bug is fixed upstream, cf https://soprani.ca/todo/390
296
298
  for v in value:
297
299
  if v not in self.__acceptable_options():
300
+ if not v:
301
+ continue
298
302
  raise XMPPError("not-acceptable", f"Not a valid option: '{v}'")
299
303
  if self.type == "list-multi":
300
- return value
301
- return [JID(v) for v in value]
304
+ return [v for v in value if v]
305
+ return [JID(v) for v in value if v]
302
306
 
303
307
  def get_xml(self) -> SlixFormField:
304
308
  """
slidge/command/user.py CHANGED
@@ -172,6 +172,9 @@ class Login(Command):
172
172
 
173
173
  async def run(self, session: Optional[AnyBaseSession], _ifrom, *_):
174
174
  assert session is not None
175
+ if session.is_logging_in:
176
+ raise XMPPError("bad-request", "You are already logging in.")
177
+ session.is_logging_in = True
175
178
  try:
176
179
  msg = await session.login()
177
180
  except Exception as e:
@@ -179,6 +182,8 @@ class Login(Command):
179
182
  raise XMPPError(
180
183
  "internal-server-error", etype="wait", text=f"Could not login: {e}"
181
184
  )
185
+ finally:
186
+ session.is_logging_in = False
182
187
  session.logged = True
183
188
  session.send_gateway_status(msg or "Re-connected", show="chat")
184
189
  session.send_gateway_message(msg or "Re-connected")
slidge/contact/contact.py CHANGED
@@ -448,13 +448,13 @@ class LegacyContact(
448
448
  log.debug("Roster push request by plugin ignored (--no-roster-push)")
449
449
  return
450
450
  try:
451
- await self._set_roster(
451
+ await self.xmpp["xep_0356"].set_roster(
452
452
  jid=self.user_jid, roster_items=self.get_roster_item()
453
453
  )
454
454
  except PermissionError:
455
455
  warnings.warn(
456
456
  "Slidge does not have privileges to add contacts to the roster. Refer"
457
- " to https://slidge.codeberg.page/docs/main/admin/privilege.html for"
457
+ " to https://slidge.im/docs/slidge/main/admin/privilege.html for"
458
458
  " more info."
459
459
  )
460
460
  if config.ROSTER_PUSH_PRESENCE_SUBSCRIPTION_REQUEST_FALLBACK:
@@ -490,12 +490,6 @@ class LegacyContact(
490
490
  nick,
491
491
  )
492
492
 
493
- async def _set_roster(self, **kw):
494
- try:
495
- await self.xmpp["xep_0356"].set_roster(**kw)
496
- except PermissionError:
497
- await self.xmpp["xep_0356_old"].set_roster(**kw)
498
-
499
493
  def send_friend_request(self, text: Optional[str] = None):
500
494
  presence = self._make_presence(ptype="subscribe", pstatus=text, bare=True)
501
495
  self._send(presence, nick=True)
slidge/core/config.py CHANGED
@@ -68,7 +68,6 @@ USER_JID_VALIDATOR__DYNAMIC_DEFAULT = True
68
68
  ADMINS: tuple[JIDType, ...] = ()
69
69
  ADMINS__DOC = "JIDs of the gateway admins"
70
70
 
71
-
72
71
  UPLOAD_SERVICE: Optional[str] = None
73
72
  UPLOAD_SERVICE__DOC = (
74
73
  "JID of an HTTP upload service the gateway can use. "
@@ -76,9 +75,6 @@ UPLOAD_SERVICE__DOC = (
76
75
  "discovery."
77
76
  )
78
77
 
79
- SECRET_KEY: Optional[str] = None
80
- SECRET_KEY__DOC = "Encryption for disk storage. Deprecated."
81
-
82
78
  NO_ROSTER_PUSH = False
83
79
  NO_ROSTER_PUSH__DOC = "Do not fill users' rosters with legacy contacts automatically"
84
80
 
@@ -187,7 +183,7 @@ MAM_MAX_DAYS__DOC = "Maximum number of days for group archive retention."
187
183
  CORRECTION_EMPTY_BODY_AS_RETRACTION = True
188
184
  CORRECTION_EMPTY_BODY_AS_RETRACTION__DOC = (
189
185
  "Treat last message correction to empty message as a retraction. "
190
- "(this is what cheogram do for retraction)"
186
+ "(this is what cheogram does for retraction)"
191
187
  )
192
188
 
193
189
  ATTACHMENT_MAXIMUM_FILE_NAME_LENGTH = 200
@@ -220,3 +216,14 @@ STRIP_LEADING_EMOJI_ADHOC__DOC = (
220
216
  "Strip the leading emoji in ad-hoc command names, if present, in case you "
221
217
  "are a emoji-hater."
222
218
  )
219
+
220
+ COMPONENT_NAME: Optional[str] = None
221
+ COMPONENT_NAME__DOC = (
222
+ "Overrides the default component name with a custom one. This is seen in service discovery and as the nickname "
223
+ "of the component in chat windows."
224
+ )
225
+
226
+ WELCOME_MESSAGE: Optional[str] = None
227
+ WELCOME_MESSAGE__DOC = (
228
+ "Overrides the default welcome message received by newly registered users."
229
+ )
slidge/core/gateway.py CHANGED
@@ -254,6 +254,10 @@ class BaseGateway(
254
254
  http: aiohttp.ClientSession
255
255
 
256
256
  def __init__(self):
257
+ if config.COMPONENT_NAME:
258
+ self.COMPONENT_NAME = config.COMPONENT_NAME
259
+ if config.WELCOME_MESSAGE:
260
+ self.WELCOME_MESSAGE = config.WELCOME_MESSAGE
257
261
  self.log = log
258
262
  self.datetime_started = datetime.now()
259
263
  self.xmpp = self # ugly hack to work with the BaseSender mixin :/
@@ -416,8 +420,14 @@ class BaseGateway(
416
420
  await self.plugin["xep_0115"].update_caps(jid=self.boundjid)
417
421
 
418
422
  if self.COMPONENT_AVATAR is not None:
419
- cached_avatar = await avatar_cache.convert_or_get(self.COMPONENT_AVATAR)
420
- self.avatar_pk = cached_avatar.pk
423
+ try:
424
+ cached_avatar = await avatar_cache.convert_or_get(self.COMPONENT_AVATAR)
425
+ except Exception as e:
426
+ log.exception("Could not set the component avatar.", exc_info=e)
427
+ cached_avatar = None
428
+ else:
429
+ assert cached_avatar is not None
430
+ self.avatar_pk = cached_avatar.pk
421
431
  else:
422
432
  cached_avatar = None
423
433
 
@@ -505,6 +515,7 @@ class BaseGateway(
505
515
  @timeit
506
516
  async def login_wrap(self, session: "BaseSession"):
507
517
  session.send_gateway_status("Logging in…", show="dnd")
518
+ session.is_logging_in = True
508
519
  try:
509
520
  status = await session.login()
510
521
  except Exception as e:
@@ -798,7 +809,9 @@ class BaseGateway(
798
809
 
799
810
  async def unregister_user(self, user: GatewayUser):
800
811
  self.send_presence(
801
- pshow="busy", pstatus="You are not registered to this gateway anymore."
812
+ pshow="dnd",
813
+ pstatus="You unregistered from this gateway.",
814
+ pto=user.jid,
802
815
  )
803
816
  await self.xmpp.plugin["xep_0077"].api["user_remove"](None, None, user.jid)
804
817
  await self.xmpp.session_cls.kill_by_jid(user.jid)
@@ -903,7 +916,6 @@ SLIXMPP_PLUGINS = [
903
916
  "xep_0333", # Chat markers
904
917
  "xep_0334", # Message Processing Hints
905
918
  "xep_0356", # Privileged Entity
906
- "xep_0356_old", # Privileged Entity (old namespace)
907
919
  "xep_0363", # HTTP file upload
908
920
  "xep_0385", # Stateless in-line media sharing
909
921
  "xep_0402", # PEP Native Bookmarks
@@ -178,6 +178,7 @@ class AttachmentMixin(TextMessageMixin):
178
178
  file_path = temp_dir / file_name
179
179
  if file_url:
180
180
  async with self.session.http.get(file_url) as r:
181
+ r.raise_for_status()
181
182
  with file_path.open("wb") as f:
182
183
  f.write(await r.read())
183
184
 
@@ -177,15 +177,12 @@ class CarbonMessageMixin(ContentMessageMixin, MarkerMixin):
177
177
  try:
178
178
  self.xmpp["xep_0356"].send_privileged_message(msg)
179
179
  except PermissionError:
180
- try:
181
- self.xmpp["xep_0356_old"].send_privileged_message(msg)
182
- except PermissionError:
183
- warnings.warn(
184
- "Slidge does not have privileges to send message on behalf of"
185
- " user.Refer to"
186
- " https://slidge.codeberg.page/docs/main/admin/privilege.html"
187
- " for more info."
188
- )
180
+ warnings.warn(
181
+ "Slidge does not have privileges to send message on behalf of"
182
+ " user.Refer to"
183
+ " https://slidge.im/docs/slidge/main/admin/privilege.html"
184
+ " for more info."
185
+ )
189
186
 
190
187
 
191
188
  class InviteMixin(MessageMaker):
slidge/core/pubsub.py CHANGED
@@ -207,7 +207,8 @@ class PubSubComponent(NamedLockMixin, BasePlugin):
207
207
  ) -> PepAvatar:
208
208
  if stanza.get_to() == self.xmpp.boundjid.bare:
209
209
  item = PepAvatar()
210
- item.set_avatar_from_cache(avatar_cache.get_by_pk(self.xmpp.avatar_pk))
210
+ if hasattr(self.xmpp, "avatar_pk"):
211
+ item.set_avatar_from_cache(avatar_cache.get_by_pk(self.xmpp.avatar_pk))
211
212
  return item
212
213
 
213
214
  if contact is None:
slidge/core/session.py CHANGED
@@ -33,7 +33,7 @@ from ..util.types import (
33
33
  ResourceDict,
34
34
  Sticker,
35
35
  )
36
- from ..util.util import deprecated
36
+ from ..util.util import deprecated, noop_coro
37
37
 
38
38
  if TYPE_CHECKING:
39
39
  from ..group.participant import LegacyParticipant
@@ -98,6 +98,7 @@ class BaseSession(
98
98
  self.ignore_messages = set[str]()
99
99
 
100
100
  self.contacts: LegacyRoster = LegacyRoster.get_self_or_unique_subclass()(self)
101
+ self.is_logging_in = False
101
102
  self._logged = False
102
103
  self.__reset_ready()
103
104
 
@@ -527,11 +528,13 @@ class BaseSession(
527
528
 
528
529
  @logged.setter
529
530
  def logged(self, v: bool):
531
+ self.is_logging_in = False
530
532
  self._logged = v
531
533
  if self.ready.done():
532
534
  if v:
533
535
  return
534
536
  self.__reset_ready()
537
+ self.shutdown(logout=False)
535
538
  else:
536
539
  if v:
537
540
  self.ready.set_result(True)
@@ -539,12 +542,15 @@ class BaseSession(
539
542
  def __repr__(self):
540
543
  return f"<Session of {self.user_jid}>"
541
544
 
542
- def shutdown(self) -> asyncio.Task:
545
+ def shutdown(self, logout=True) -> asyncio.Task:
543
546
  for c in self.contacts:
544
547
  c.offline()
545
548
  for m in self.bookmarks:
546
549
  m.shutdown()
547
- return self.xmpp.loop.create_task(self.logout())
550
+ if logout:
551
+ return self.xmpp.loop.create_task(self.logout())
552
+ else:
553
+ return self.xmpp.loop.create_task(noop_coro())
548
554
 
549
555
  @staticmethod
550
556
  def legacy_to_xmpp_msg_id(legacy_msg_id: LegacyMessageType) -> str:
@@ -657,6 +663,8 @@ class BaseSession(
657
663
  return
658
664
  for c in session.contacts:
659
665
  c.unsubscribe()
666
+ for m in session.bookmarks:
667
+ m.shutdown()
660
668
  user = cls.xmpp.store.users.get(jid)
661
669
  if user is None:
662
670
  log.warning("User not found during unregistration")
@@ -0,0 +1,33 @@
1
+ """Add Participant.nickname_no_illegal
2
+
3
+ Revision ID: 04cf35e3cf85
4
+ Revises: 15b0bd83407a
5
+ Create Date: 2025-02-22 06:57:45.491326
6
+
7
+ """
8
+
9
+ from typing import Sequence, Union
10
+
11
+ import sqlalchemy as sa
12
+ from alembic import op
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = "04cf35e3cf85"
16
+ down_revision: Union[str, None] = "15b0bd83407a"
17
+ branch_labels: Union[str, Sequence[str], None] = None
18
+ depends_on: Union[str, Sequence[str], None] = None
19
+
20
+
21
+ def upgrade() -> None:
22
+ # ### commands auto generated by Alembic - please adjust! ###
23
+ with op.batch_alter_table("participant", schema=None) as batch_op:
24
+ batch_op.add_column(
25
+ sa.Column("nickname_no_illegal", sa.String(), nullable=True)
26
+ )
27
+
28
+ # ### end Alembic commands ###
29
+
30
+
31
+ def downgrade() -> None:
32
+ with op.batch_alter_table("participant", schema=None) as batch_op:
33
+ batch_op.drop_column("nickname_no_illegal")
@@ -8,8 +8,6 @@ Create Date: 2024-04-17 20:57:01.357041
8
8
 
9
9
  """
10
10
 
11
- import logging
12
- from datetime import datetime
13
11
  from typing import Sequence, Union
14
12
 
15
13
  import sqlalchemy as sa
@@ -44,58 +42,9 @@ def upgrade() -> None:
44
42
  sa.UniqueConstraint("jid"),
45
43
  )
46
44
  # ### end Alembic commands ###
47
- try:
48
- migrate_from_shelf(accounts)
49
- except Exception:
50
- downgrade()
51
- raise
52
45
 
53
46
 
54
47
  def downgrade() -> None:
55
48
  # ### commands auto generated by Alembic - please adjust! ###
56
49
  op.drop_table("user_account")
57
50
  # ### end Alembic commands ###
58
-
59
-
60
- def migrate_from_shelf(accounts: sa.Table) -> None:
61
- from slidge import global_config
62
-
63
- home = getattr(global_config, "HOME_DIR", None)
64
- if home is None:
65
- return
66
-
67
- db_file = home / "slidge.db"
68
- if not db_file.exists():
69
- return
70
-
71
- try:
72
- from slidge.db.alembic.old_user_store import user_store
73
- except ImportError:
74
- return
75
-
76
- user_store.set_file(db_file, global_config.SECRET_KEY)
77
-
78
- try:
79
- users = list(user_store.get_all())
80
- except AttributeError:
81
- return
82
- logging.info("Migrating %s users from the deprecated user_store shelf", len(users))
83
- op.bulk_insert(
84
- accounts,
85
- [
86
- {
87
- "jid": user.jid,
88
- "registration_date": (
89
- user.registration_date
90
- if user.registration_date is not None
91
- else datetime.now()
92
- ),
93
- "legacy_module_data": user.registration_form,
94
- "preferences": {},
95
- }
96
- for user in users
97
- ],
98
- )
99
-
100
- user_store.close()
101
- db_file.unlink()
slidge/db/avatar.py CHANGED
@@ -14,6 +14,7 @@ from multidict import CIMultiDictProxy
14
14
  from PIL.Image import Image
15
15
  from PIL.Image import open as open_image
16
16
  from sqlalchemy import select
17
+ from sqlalchemy.orm.exc import DetachedInstanceError
17
18
 
18
19
  from slidge.core import config
19
20
  from slidge.db.models import Avatar
@@ -102,6 +103,7 @@ class AvatarCache:
102
103
  if response.status == HTTPStatus.NOT_MODIFIED:
103
104
  log.debug("Using avatar cache for %s", url)
104
105
  raise NotModified
106
+ response.raise_for_status()
105
107
  return (
106
108
  open_image(io.BytesIO(await response.read())),
107
109
  response.headers,
@@ -141,7 +143,24 @@ class AvatarCache:
141
143
  )
142
144
  except NotModified:
143
145
  assert stored is not None
144
- return CachedAvatar.from_store(stored, self.dir)
146
+ try:
147
+ return CachedAvatar.from_store(stored, self.dir)
148
+ except DetachedInstanceError:
149
+ # This is an awful hack to prevent errors on startup under certain conditions,
150
+ # because we basically misused SQLAlchemy pretty bad in slidge.db.store.EngineMixin.session().
151
+ # cf https://codeberg.org/slidge/slidge/issues/36
152
+ # and https://docs.sqlalchemy.org/en/20/orm/session_basics.html#session-faq-threadsafe
153
+ # It may be related to threads as we only have reports of this for slidge-whatsapp and skidge
154
+ # which are the only implementations that uses threads.
155
+ # In any case, a proper fix implies a major refactoring in which we spawn and close SQLAlchemy
156
+ # "ORM Session"s with a reasonable, well-thought lifetime, instead of how we do it now, where we
157
+ # basically just brute-forced our way into having something usable but with poor performance.
158
+ # Databases, asyncio, and concurrency in general are hard… :(
159
+ # Oh, and getting rid of the convoluted mess that this giant method is would also probably
160
+ # be a good idea.
161
+ stored = self.store.get_by_url(avatar)
162
+ assert stored is not None
163
+ return CachedAvatar.from_store(stored, self.dir)
145
164
  else:
146
165
  img = await self._get_image(avatar)
147
166
  response_headers = None
slidge/db/models.py CHANGED
@@ -388,6 +388,7 @@ class Participant(Base):
388
388
 
389
389
  resource: Mapped[Optional[str]] = mapped_column(default=None)
390
390
  nickname: Mapped[str] = mapped_column(nullable=True, default=None)
391
+ nickname_no_illegal: Mapped[str] = mapped_column(nullable=True, default=None)
391
392
 
392
393
  hats: Mapped[list["Hat"]] = relationship(
393
394
  secondary=participant_hats, back_populates="participants"
slidge/db/store.py CHANGED
@@ -58,6 +58,8 @@ class EngineMixin:
58
58
  def __init__(self, engine: Engine):
59
59
  self._engine = engine
60
60
 
61
+ # TODO: we should not have a global Session object but instead build Sessions with different parameters
62
+ # depending on the context (startup, incoming XMPP event, incoming legacy event).
61
63
  @contextmanager
62
64
  def session(self, **session_kwargs) -> Iterator[Session]:
63
65
  global _session
@@ -696,6 +698,8 @@ class MultiStore(EngineMixin):
696
698
  ).scalar()
697
699
  if multi is None:
698
700
  return []
701
+ if multi.legacy_ids_multi is None:
702
+ return []
699
703
  return [m.xmpp_id for m in multi.legacy_ids_multi.xmpp_ids]
700
704
 
701
705
  def set_xmpp_ids(
@@ -1070,6 +1074,7 @@ class ParticipantStore(EngineMixin):
1070
1074
  .where(Participant.id == participant.pk)
1071
1075
  .values(
1072
1076
  resource=participant.jid.resource,
1077
+ nickname_no_illegal=participant._nickname_no_illegal,
1073
1078
  affiliation=participant.affiliation,
1074
1079
  role=participant.role,
1075
1080
  presence_sent=participant._presence_sent, # type:ignore
@@ -70,6 +70,8 @@ class LegacyParticipant(
70
70
  is_system=False,
71
71
  role: MucRole = "participant",
72
72
  affiliation: MucAffiliation = "member",
73
+ resource: str | None = None,
74
+ nickname_no_illegal: str | None = None,
73
75
  ):
74
76
  self.session = session = muc.session
75
77
  self.xmpp = session.xmpp
@@ -83,7 +85,14 @@ class LegacyParticipant(
83
85
 
84
86
  self._nickname = nickname
85
87
 
86
- self.__update_jid(nickname)
88
+ if resource is None:
89
+ self.__update_jid(nickname)
90
+ else:
91
+ assert nickname_no_illegal is not None
92
+ self._nickname_no_illegal = nickname_no_illegal
93
+ self.jid = JID(self.muc.jid)
94
+ self.jid.resource = resource
95
+
87
96
  log.debug("Instantiation of: %r", self)
88
97
 
89
98
  self.contact: Optional["LegacyContact"] = None
@@ -510,6 +519,8 @@ class LegacyParticipant(
510
519
  stored.nickname,
511
520
  role=stored.role,
512
521
  affiliation=stored.affiliation,
522
+ resource=stored.resource,
523
+ nickname_no_illegal=stored.nickname_no_illegal,
513
524
  )
514
525
  part.pk = stored.id
515
526
  if contact is not None:
slidge/group/room.py CHANGED
@@ -423,7 +423,7 @@ class LegacyMUC(
423
423
 
424
424
  if subject_setter == self._subject_setter:
425
425
  return
426
- assert isinstance(subject_setter, str)
426
+ assert isinstance(subject_setter, str | None)
427
427
  self._subject_setter = subject_setter
428
428
  if self._updating_info:
429
429
  return
@@ -20,7 +20,6 @@ from . import (
20
20
  xep_0100,
21
21
  xep_0153,
22
22
  xep_0292,
23
- xep_0356_old,
24
23
  xep_0492,
25
24
  )
26
25
 
@@ -148,7 +147,6 @@ slixmpp.plugins.PLUGINS.extend(
148
147
  [
149
148
  "link_preview",
150
149
  "xep_0292_provider",
151
- "xep_0356_old",
152
150
  "xep_0492",
153
151
  ]
154
152
  )
@@ -93,10 +93,7 @@ class XEP_0100(BasePlugin):
93
93
  self.xmpp.send_presence(ptype="subscribe", pto=jid.bare)
94
94
 
95
95
  async def _set_roster(self, jid, items):
96
- try:
97
- await self.xmpp["xep_0356"].set_roster(jid=jid.bare, roster_items=items)
98
- except PermissionError:
99
- await self.xmpp["xep_0356_old"].set_roster(jid=jid.bare, roster_items=items)
96
+ await self.xmpp["xep_0356"].set_roster(jid=jid.bare, roster_items=items)
100
97
 
101
98
  def on_presence_unsubscribe(self, p: Presence):
102
99
  if p.get_to() == self.xmpp.boundjid.bare:
slidge/util/util.py CHANGED
@@ -321,7 +321,7 @@ def timeit(func):
321
321
  async def wrapped(self, *args, **kwargs):
322
322
  start = time()
323
323
  r = await func(self, *args, **kwargs)
324
- self.log.info("%s took %s ms", func.__name__, round((time() - start) * 1000))
324
+ self.log.debug("%s took %s ms", func.__name__, round((time() - start) * 1000))
325
325
  return r
326
326
 
327
327
  return wrapped
@@ -336,3 +336,7 @@ def strip_leading_emoji(text: str) -> str:
336
336
  if len(words) > 1 and emoji.purely_emoji(words[0]):
337
337
  return " ".join(words[1:])
338
338
  return text
339
+
340
+
341
+ async def noop_coro():
342
+ pass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: slidge
3
- Version: 0.2.4
3
+ Version: 0.2.6
4
4
  Summary: XMPP bridging framework
5
5
  Author-email: Nicolas Cedilnik <nicoco@nicoco.fr>
6
6
  License: GNU AFFERO GENERAL PUBLIC LICENSE
@@ -669,22 +669,22 @@ Project-URL: Homepage, https://codeberg.org/slidge/
669
669
  Project-URL: Issues, https://codeberg.org/slidge/slidge/issues
670
670
  Project-URL: Repository, https://codeberg.org/slidge/slidge/
671
671
  Project-URL: Chat room, https://conference.nicoco.fr:5281/muc_log/slidge/
672
- Project-URL: Documentation, https://slidge.codeberg.page/docs/main
672
+ Project-URL: Documentation, https://slidge.im/docs/slidge/main
673
+ Project-URL: changelog, https://codeberg.org/slidge/slidge/releases
673
674
  Keywords: xmpp,gateway,bridge,instant messaging
674
675
  Classifier: Topic :: Internet :: XMPP
675
676
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
676
677
  Requires-Python: >=3.11
677
678
  Description-Content-Type: text/markdown
678
- Requires-Dist: aiohttp[speedups]>=3.11.11
679
- Requires-Dist: alembic>=1.14.0
680
- Requires-Dist: configargparse>=1.7
679
+ Requires-Dist: aiohttp[speedups]<4,>=3.11.11
680
+ Requires-Dist: alembic<2,>=1.14.0
681
+ Requires-Dist: configargparse<2,>=1.7
681
682
  Requires-Dist: defusedxml>=0.7.1
682
- Requires-Dist: pickle-secure>=0.99.9
683
- Requires-Dist: pillow>=11.0.0
684
- Requires-Dist: python-magic>=0.4.27
685
- Requires-Dist: qrcode>=8.0
686
- Requires-Dist: slixmpp>=1.8.6
687
- Requires-Dist: sqlalchemy>=2.0.36
683
+ Requires-Dist: pillow<12,>=11.0.0
684
+ Requires-Dist: python-magic<0.5,>=0.4.27
685
+ Requires-Dist: qrcode<9,>=8.0
686
+ Requires-Dist: slixmpp<2,>=1.8.6
687
+ Requires-Dist: sqlalchemy<3,>=2
688
688
  Requires-Dist: thumbhash>=0.1.2
689
689
 
690
690
  ![Slidge logo](https://codeberg.org/slidge/slidge/raw/branch/main/dev/assets/slidge-color-small.png)
@@ -693,14 +693,14 @@ Requires-Dist: thumbhash>=0.1.2
693
693
 
694
694
 
695
695
  [![woodpecker CI status](https://ci.codeberg.org/api/badges/14027/status.svg)](https://ci.codeberg.org/repos/14027)
696
- [![coverage](https://slidge.codeberg.page/coverage/main/coverage.svg)](https://slidge.im/coverage/main)
696
+ [![coverage](https://slidge.im/coverage/main/coverage.svg)](https://slidge.im/coverage/main)
697
697
 
698
698
  [![pypi version](https://badge.fury.io/py/slidge.svg)](https://pypi.org/project/slidge/)
699
699
  [![debian unstable version](https://badges.debian.net/badges/debian/unstable/python3-slidge/version.svg)](https://packages.debian.org/unstable/python3-slidge)
700
700
 
701
701
  Slidge is an XMPP (puppeteer) gateway library in python.
702
702
  It makes
703
- [writing gateways to other chat networks](https://slidge.im/core/dev/tutorial.html)
703
+ [writing gateways to other chat networks](https://slidge.im/docs/slidge/main/dev/tutorial.html)
704
704
  (*legacy modules*) as frictionless as possible.
705
705
  It supports fancy IM features, such as
706
706
  [(emoji) reactions](https://xmpp.org/extensions/xep-0444.html),
@@ -751,7 +751,7 @@ class Session(BaseSession):
751
751
  self.legacy_client.send_message(text=text, destination=chat.legacy_id)
752
752
  ```
753
753
 
754
- There's more in [the tutorial](https://slidge.codeberg.page/docs/main/dev/tutorial.html)!
754
+ There's more in [the tutorial](https://slidge.im/docs/slidge/main/dev/tutorial.html)!
755
755
 
756
756
  Installation
757
757
  ------------
@@ -766,7 +766,7 @@ bundle.
766
766
  Slidge is available on
767
767
  [codeberg](https://codeberg.org/slidge/-/packages) (python packages and containers)
768
768
  and [pypi](https://pypi.org/project/slidge/).
769
- Refer to [the docs](https://slidge.codeberg.page/docs/main/admin/install.html) for details.
769
+ Refer to [the docs](https://slidge.im/docs/slidge/main/admin/install.html) for details.
770
770
 
771
771
  About privacy
772
772
  -------------
@@ -1,25 +1,25 @@
1
1
  slidge/__init__.py,sha256=S0tUjqpZlzsr8G4Y_1Xt-KCYB07qaknTB0OwHU8k29U,1587
2
2
  slidge/__main__.py,sha256=ydjUklOoavS4YlGfjRX_8BQN2DaSbaXPMi47RkOgcFI,37
3
- slidge/__version__.py,sha256=ieOsYgM0aVfIKxAjpsbCrB7YTVXvXet9-wS_3Z87ito,165
3
+ slidge/__version__.py,sha256=LukT7r82VsYYhRUhnpDW_6t1xNzuYY3v380h-wU8KCg,165
4
4
  slidge/main.py,sha256=vMJzhvUxbeuIXuHxXXs6lm_ShBjXiS9B5Li5Ge4vWPo,6238
5
5
  slidge/migration.py,sha256=4BJmPIRB56_WIhRTqBFIIBXuvnhhBjjOMl4CE7jY6oc,1541
6
6
  slidge/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  slidge/command/__init__.py,sha256=UYf1mjCYbZ5G7PIgaFTWSQRAzEJkQ6dTH8Fu_e_XnO0,613
8
- slidge/command/adhoc.py,sha256=-AO4h1N6owSuuqZon5tDL29O6qmEeAd1pcPjGCkzKRs,10065
8
+ slidge/command/adhoc.py,sha256=FEkE5UapIP_yrmrzGn5yIHCubOZijoI_muu1AtCqGOM,10472
9
9
  slidge/command/admin.py,sha256=TYrzgCIhjcTIwl1IUaFlUd3D98SPyao10gB20zo8b3Q,6187
10
- slidge/command/base.py,sha256=S-4WnxDuGE3sQTca5L6DjHVBsHqpNuf6zvXffYoP46c,13159
10
+ slidge/command/base.py,sha256=EDcEl5dJcooSmLarXI2fmBq6QtU7h-7MOM3DDsxXmTU,13447
11
11
  slidge/command/categories.py,sha256=vF0KGDV9sEn8TNkcMoDRw-u3gEyNHSXghOU2JRHQtKs,351
12
12
  slidge/command/chat_command.py,sha256=z-4qp03rK7kCh3_kEozDViwkDg_hVjHvRCiYYJxedBQ,11153
13
13
  slidge/command/register.py,sha256=BduDI31Kx8CbWWEdjybimTA5Wcfhn-Jkt8sSPsySCpo,6724
14
- slidge/command/user.py,sha256=aksfkRUK7xj9n0XM-IfUSS5LrTp0qIHt5K2UkBBT1UE,12099
14
+ slidge/command/user.py,sha256=fLh5d7XTSXicj6g0I80F5n6BFaA20PaYoXFkfDOR4zA,12303
15
15
  slidge/contact/__init__.py,sha256=WMMaHk7UW7YT9EH2LtPdkU0bHQaOp4ikBhbBQskmoc8,191
16
- slidge/contact/contact.py,sha256=BQ5Fw7InnI76S5bQi_kZL1HhtptZhegmtsKksml7tjY,23125
16
+ slidge/contact/contact.py,sha256=ITcjW_3VMdVEHQfqxscpIULnofZBJ841wvnYwOvk1Tc,22934
17
17
  slidge/contact/roster.py,sha256=x3speGdHbZ-VTLoQLQW4s53rBeBvW87W8ZibCCZSLDA,10300
18
18
  slidge/core/__init__.py,sha256=RG7Jj5JCJERjhqJ31lOLYV-7bH_oblClQD1KF9LsTXo,68
19
- slidge/core/config.py,sha256=WP3-ScXqdAhJBX7IRB5pBi_tAV_vE6G5W3Z-LGGULQw,7691
20
- slidge/core/gateway.py,sha256=Rid4Zc_IIV-3lMiiBirqiezkxC_Ed1HMlb9HLyxlnJc,36594
21
- slidge/core/pubsub.py,sha256=oTiS5KFQJAmsgkhOsvfvthT-LkuZGQSCrrUG0JskNkI,11907
22
- slidge/core/session.py,sha256=nQexpCd1jlHOhQPnFI4ri-5odp3N2pU5HO4l7WFetZY,28148
19
+ slidge/core/config.py,sha256=OjJfpXJaDhMxRB-vYA0cqkSf0fwMt-HMThM8GS1htCg,7964
20
+ slidge/core/gateway.py,sha256=qdvLGh5GF823mACVw5j1WLYMzsSmeGpmQFuWvz5Z9AU,37028
21
+ slidge/core/pubsub.py,sha256=BoeYE__ptmRAn4x55Hn_6JWRA4nM-XJgDemG5Cy5kN4,11959
22
+ slidge/core/session.py,sha256=Y6psOKm_lv4q7yXARiLuijvSebuS64NjqSNF1WARtHM,28439
23
23
  slidge/core/dispatcher/__init__.py,sha256=1EXcjXietUKlxEqdrCWCV3xZ3q_DSsjHoqWrPMbtYao,84
24
24
  slidge/core/dispatcher/caps.py,sha256=vzCAXo_bhALuLEpJWtyJTzVfWx96g1AsWD8_wkoDl0Y,2028
25
25
  slidge/core/dispatcher/disco.py,sha256=j56VY9NIFzwPEWFKQQZ7YIqS9GdD-ZaF_K8a2L-JvRk,2006
@@ -40,26 +40,26 @@ slidge/core/dispatcher/muc/misc.py,sha256=bHBjMC-Pu3jR5hAPGMzXf-C05UbACIwg38YbJU
40
40
  slidge/core/dispatcher/muc/owner.py,sha256=1a6YV7b_mmi1jC6q1ko8weeL8imQA-s-hYGPLIHd10I,3308
41
41
  slidge/core/dispatcher/muc/ping.py,sha256=lb1VQPhiUPZ19KhbofRXMVCcY6wwQ2w-asnqtANaAwA,1660
42
42
  slidge/core/mixins/__init__.py,sha256=muReAzgvENgMvlfm0Fpe6BQFfm2EMjoDe9ZhGgo6Vig,627
43
- slidge/core/mixins/attachment.py,sha256=sdRo_WMaWnWr00bM3AYyZRAsP0TlgwqkdHd1i2EAkCg,20133
43
+ slidge/core/mixins/attachment.py,sha256=rZcipXiGgo-8klumBvfgrSsU_rmS6cl1zx-zev_FZRg,20174
44
44
  slidge/core/mixins/avatar.py,sha256=BVOeH5j5tsPJ0IHUcB5ozpyh02TBPKiZMd9MYizDokA,8136
45
45
  slidge/core/mixins/base.py,sha256=MOd-pas38_52VawQVlxWtBtmTKC6My9G0ZaCeQxOJbs,748
46
46
  slidge/core/mixins/db.py,sha256=5Qpegd7D8e5TLXLLINYcf_DuVdN-7wNmsfztUuFYPcU,442
47
47
  slidge/core/mixins/disco.py,sha256=jk3Z1B6zTuisHv8VKNRJodIo0ee5btYHh2ZrlflPj_Q,3670
48
48
  slidge/core/mixins/lock.py,sha256=Vf1rrkbyNbSprr38WGfZiMgTB7AdbqH8ppFHY8N2yXE,975
49
- slidge/core/mixins/message.py,sha256=5Tt1r_8rjsV445b_28Lx73S-rW5tsP1uGPQhIKN64VM,7998
49
+ slidge/core/mixins/message.py,sha256=X8Ka8j0nOnBcecYE_YuK8_J7MeO5-R0TIZw4X8c7R3Y,7846
50
50
  slidge/core/mixins/message_maker.py,sha256=TcCutHi0sIwL6beJNkN7XyR0aDIbA0xZyxd2Gc9ulG4,6022
51
51
  slidge/core/mixins/message_text.py,sha256=pCY4tezEuwB2ZuUyUi72i4v9AJkxp_SWF1jrFsn94Ns,8096
52
52
  slidge/core/mixins/presence.py,sha256=yywo6KAw8C7GaZSMrSMuioNfhW08MrnobHt8XbHd0q8,7891
53
53
  slidge/core/mixins/recipient.py,sha256=b0uFnpym-hOFgYxGjXT1xQcZ4YRbDSBftPcNWLzSwEI,1336
54
54
  slidge/db/__init__.py,sha256=EBDH1JSEhgqYcli2Bw11CRC749wJk8AOucgBzmhDSvU,105
55
- slidge/db/avatar.py,sha256=FfRt2Vu11ZKD9F3x1_drawvUd-TDE3mp7SE3BZ9hOOg,6467
55
+ slidge/db/avatar.py,sha256=z5e72STv8PdN6zkNyKlLqF7NFxHwCa6IjwgFpzu5ghE,8033
56
56
  slidge/db/meta.py,sha256=v1Jf-npZ28QwdGpsLQWLBHEbEP3-jnPrygRg05tJ_Iw,1831
57
- slidge/db/models.py,sha256=0NUJfa3lPKHhwV2zE4p7QVYCVs3yn9egFg2u9mssk5c,13964
58
- slidge/db/store.py,sha256=4QyK6rjztPdmhjsqxMookEK_WrdlzOpPFPWnCpKy2u4,46704
57
+ slidge/db/models.py,sha256=MSVNW04x05qfxahvjCYRDFjfFP-XXp-lOHnK5IqFCXw,14046
58
+ slidge/db/store.py,sha256=ETk6oUUEz8-YUFGCh7nspSJH7dFOj8qeIWLQhX4nOH8,47051
59
59
  slidge/db/alembic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
60
  slidge/db/alembic/env.py,sha256=hsBlRNs0zF5diSHGRSa8Fi3qRVQDA2rJdR41AEIdvxc,1642
61
- slidge/db/alembic/old_user_store.py,sha256=zFOv0JEWQQK0_TMRlU4Z0G5Mc9pxvEErLyOzXmRAe5Q,5209
62
61
  slidge/db/alembic/script.py.mako,sha256=MEqL-2qATlST9TAOeYgscMn1uy6HUS9NFvDgl93dMj8,635
62
+ slidge/db/alembic/versions/04cf35e3cf85_add_participant_nickname_no_illegal.py,sha256=Dwz_azOXr7Tsw7Wnj0L8mknITIPXO9ewEsRn169EUNA,904
63
63
  slidge/db/alembic/versions/09f27f098baa_add_missing_attributes_in_room.py,sha256=mUL-0Io6ZPd_QbnKfwGYyjdMcM2uxQ0Wg72H23-2t_E,1033
64
64
  slidge/db/alembic/versions/15b0bd83407a_remove_bogus_unique_constraints_on_room_.py,sha256=kzHuHGhzey5CY0p_OsKf5a-3zSk2649wqg2ToLiSD1I,2927
65
65
  slidge/db/alembic/versions/2461390c0af2_store_contacts_caps_verstring_in_db.py,sha256=CLB-kOP9Rc0FJIKDLef912L5sYkjpTIPC8fhrIdrC7k,1084
@@ -71,7 +71,7 @@ slidge/db/alembic/versions/5bd48bfdffa2_lift_room_legacy_id_constraint.py,sha256
71
71
  slidge/db/alembic/versions/82a4af84b679_add_muc_history_filled.py,sha256=g37po0ydp8ZmzJrE5oFV7GscnploxjCtPDpw28SqVGk,1429
72
72
  slidge/db/alembic/versions/8b993243a536_add_vcard_content_to_contact_table.py,sha256=18tG8B03Kq8Qz_-mMd28Beed6jow8XNTtrz7gT5QY3g,1210
73
73
  slidge/db/alembic/versions/8d2ced764698_rely_on_db_to_store_contacts_rooms_and_.py,sha256=ikoAlRV3_BJcDcFRANF-9HTB--0xpY0C5XdGuMuW9c0,4866
74
- slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py,sha256=XFHaHjPMoxKxKRjNBnYHBzMtS6K38ENcsGzgzlyp60g,2649
74
+ slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py,sha256=eFlfn-LiDph05yyCc8gmtJwVKzgrSHwyWJ6nuVfEpQA,1391
75
75
  slidge/db/alembic/versions/abba1ae0edb3_store_avatar_legacy_id_in_the_contact_.py,sha256=VprqEVHipYuM-ea-CIM4_ubOD5zJ9inLTbhXc869n3A,2779
76
76
  slidge/db/alembic/versions/b33993e87db3_move_everything_to_persistent_db.py,sha256=2tiRxoC9PYOQn6XQrwK0JTEsb45Pzp2PsKoZSS4rcIA,7564
77
77
  slidge/db/alembic/versions/b64b1a793483_add_source_and_legacy_id_for_archived_.py,sha256=r2sOgR5HcfueJyc3cWNDRmlZzdHOSX6nl2gef54wDbk,1559
@@ -80,9 +80,9 @@ slidge/db/alembic/versions/e91195719c2c_store_users_avatars_persistently.py,sha2
80
80
  slidge/group/__init__.py,sha256=yFt7cHqeaKIMN6f9ZyhhspOcJJvBtLedGv-iICG7lto,258
81
81
  slidge/group/archive.py,sha256=IPqklzo0UN3lPHckfsKW9c4nl3m_9XGY4u0eehrhe8k,5281
82
82
  slidge/group/bookmarks.py,sha256=AvFL34bEX6n3OP1Np309T5hrLK9GnjkjdyLJ3uiLZyc,6616
83
- slidge/group/participant.py,sha256=epxXqJ545ALkEYxziiobH149nlBeSZlxsNCYeI8LFhM,17357
84
- slidge/group/room.py,sha256=aJ-VIYACuEuoyKDsBFxsUZFoVpd265i0mzTOOHCNTPg,46849
85
- slidge/slixfix/__init__.py,sha256=aEXtmxMgxQWsUoDV5NSxZKwb-Hml5XeR2FMJlnaDUng,4324
83
+ slidge/group/participant.py,sha256=cUuyJRGq8AIHrwtubje5cyb5hHY2hGLtGboBju4SI0c,17781
84
+ slidge/group/room.py,sha256=Ar7mSSZRqjDPaXtRuq1da-ZqdJ-fibVtEZDcfpZtpXY,46856
85
+ slidge/slixfix/__init__.py,sha256=Og6_EAuWst6paWmDiGqeqQH6Iof6V8Vkr5pyYisgjsw,4282
86
86
  slidge/slixfix/delivery_receipt.py,sha256=3bWdZH3-X3CZJXmnI_TpjkTUUK-EY4Ktm78lW0-40fc,1366
87
87
  slidge/slixfix/roster.py,sha256=KvDjh9q7pqaZf69H93okfib13cc95uVZUJ6rzpqmDaU,1704
88
88
  slidge/slixfix/link_preview/__init__.py,sha256=TDPTSEH5FQxgGpQpQIde-D72AHg-6YVWG-tOj4KpKmU,290
@@ -92,27 +92,23 @@ slidge/slixfix/xep_0077/__init__.py,sha256=0lY1YXdgAsfrfxI_Woxaf1etHCJXe35Xtntq_
92
92
  slidge/slixfix/xep_0077/register.py,sha256=6nwTfHNL7Z9-1wUhpAF743TNbjQLCMP7Rflkdad8d60,10431
93
93
  slidge/slixfix/xep_0077/stanza.py,sha256=Lngly7F1ChCkNKn7yl1QmN838fO-KqkAhkazxzDsz80,2410
94
94
  slidge/slixfix/xep_0100/__init__.py,sha256=AtEXDQOrEWodkN3fgKR0W3Ezsz_Zza6cgO5ZaZS-JOo,107
95
- slidge/slixfix/xep_0100/gateway.py,sha256=Fhxs2sUPnVRPa1o7RXCncnbBO2gjeZx3pbItug-8OiA,4501
95
+ slidge/slixfix/xep_0100/gateway.py,sha256=aSR18605MWVqgucNd_zd1SdYlXry7k5uoqDzsvVqQyM,4363
96
96
  slidge/slixfix/xep_0100/stanza.py,sha256=7vCzej9VFQupsTpGGl0cJWuGNH4I6oVcckBu_-fE55c,232
97
97
  slidge/slixfix/xep_0153/__init__.py,sha256=hsEldnLuzvcp0NqSscxPV7FJl-6GFP372vlDg1G3S3I,283
98
98
  slidge/slixfix/xep_0153/vcard_avatar.py,sha256=py-qzj1jmmzsM4GCTKLRW7cAdAmSVjodp6q0r5B0RqQ,458
99
99
  slidge/slixfix/xep_0292/__init__.py,sha256=_MvS9wGra6ig3P_dPAVlCPDJkiOFvUWGjaRsHj1woUg,98
100
100
  slidge/slixfix/xep_0292/vcard4.py,sha256=jL-TOW3eG2QXLduSLNq03L8HoUNmvy8kTZI5ojvo6GE,358
101
- slidge/slixfix/xep_0356_old/__init__.py,sha256=3jGWJX2m5gWgDCxcVqCsCCVPRTcfmU96yenwvAJtOKE,180
102
- slidge/slixfix/xep_0356_old/privilege.py,sha256=kcJzFbzhOHtQMtzOJpvvwm1pghSpealWnqhC0zc8dGo,5338
103
- slidge/slixfix/xep_0356_old/stanza.py,sha256=i7aqcaTg6PBhVwbHToLtlrwxBj7uO-M7VrYSyElyEKI,1229
104
101
  slidge/slixfix/xep_0492/__init__.py,sha256=kjWVeX3SG_2ohHx0fuMh1gmM2G57Bl6SRo7Mfv6sglA,161
105
102
  slidge/slixfix/xep_0492/notify.py,sha256=8EPSdU3rTzWkHNm8oFr0tK2PmMJ6hBAIr88GoOmHTuQ,340
106
103
  slidge/slixfix/xep_0492/stanza.py,sha256=gL0ixpV07Q9eqTXIOjdLVPtim45izuwaLk0iCoZ8e7c,2649
107
104
  slidge/util/__init__.py,sha256=BELovoTMPcPPGz3D48esBr8A4BRRHXTvavfgnArBgEc,301
108
105
  slidge/util/archive_msg.py,sha256=xXAR0BI5r3d6KKWjae9594izCOv6iI03z2WLuTecNw8,1724
109
106
  slidge/util/conf.py,sha256=1j2OnOsCBar1tOObErhXR5RC3Vl3faliOZ1U8J3My58,6613
110
- slidge/util/db.py,sha256=4LxZj8oBYgiSnyBUnF_ALjr0TblkfNQq_p28sCfkHMY,242
111
107
  slidge/util/test.py,sha256=l1VHBsw5Uzk2t7wtkfb9kWvtehcYhw1t_d567JAJFKA,14135
112
108
  slidge/util/types.py,sha256=R_xfS5mRL0XUJIoDpnaAkZlTOoLPerduXBFftaVwIAI,5489
113
- slidge/util/util.py,sha256=An4BRIHktZGXnu4kCwaKYaSye_PlyuxEm_4SC9YvPhc,9594
114
- slidge-0.2.4.dist-info/METADATA,sha256=pMPAZlzk25loszDWjC73w2dvufxfN4f9NxTxxQ7KcNA,44799
115
- slidge-0.2.4.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
116
- slidge-0.2.4.dist-info/entry_points.txt,sha256=py3_x834fFJ2TEzPd18Wt2DnysdAfuVqJ5zzBrXbAZs,44
117
- slidge-0.2.4.dist-info/top_level.txt,sha256=2LRjDYHaGZ5ieCMF8xy58JIiabRMzX-MGMbCZwfE17c,7
118
- slidge-0.2.4.dist-info/RECORD,,
109
+ slidge/util/util.py,sha256=4GdRZwHYUPkteEi8oux--qLwMiXnVLl_ojMDaKbEQCE,9629
110
+ slidge-0.2.6.dist-info/METADATA,sha256=ShWfovgJtP9yPJYaEZqHg0Rd62blhfP78go2AsRfeoM,44841
111
+ slidge-0.2.6.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
112
+ slidge-0.2.6.dist-info/entry_points.txt,sha256=py3_x834fFJ2TEzPd18Wt2DnysdAfuVqJ5zzBrXbAZs,44
113
+ slidge-0.2.6.dist-info/top_level.txt,sha256=2LRjDYHaGZ5ieCMF8xy58JIiabRMzX-MGMbCZwfE17c,7
114
+ slidge-0.2.6.dist-info/RECORD,,
@@ -1,183 +0,0 @@
1
- """
2
- This module covers a backend for storing user data persistently and managing a
3
- pseudo-roster for the gateway component.
4
- """
5
-
6
- import dataclasses
7
- import datetime
8
- import logging
9
- import os.path
10
- import shelve
11
- from io import BytesIO
12
- from os import PathLike
13
- from typing import Iterable, Optional, Union
14
-
15
- from pickle_secure import Pickler, Unpickler
16
- from slixmpp import JID, Iq, Message, Presence
17
-
18
-
19
- # noinspection PyUnresolvedReferences
20
- class EncryptedShelf(shelve.DbfilenameShelf):
21
- cache: dict
22
- dict: dict
23
- writeback: bool
24
- keyencoding: str
25
- _protocol: int
26
-
27
- def __init__(
28
- self, filename: PathLike, key: str, flag="c", protocol=None, writeback=False
29
- ):
30
- super().__init__(str(filename), flag, protocol, writeback)
31
- self.secret_key = key
32
-
33
- def __getitem__(self, key):
34
- try:
35
- value = self.cache[key]
36
- except KeyError:
37
- f = BytesIO(self.dict[key.encode(self.keyencoding)])
38
- value = Unpickler(f, key=self.secret_key).load() # type:ignore
39
- if self.writeback:
40
- self.cache[key] = value
41
- return value
42
-
43
- def __setitem__(self, key, value):
44
- if self.writeback:
45
- self.cache[key] = value
46
- f = BytesIO()
47
- p = Pickler(f, self._protocol, key=self.secret_key) # type:ignore
48
- p.dump(value)
49
- self.dict[key.encode(self.keyencoding)] = f.getvalue()
50
-
51
-
52
- @dataclasses.dataclass
53
- class GatewayUser:
54
- """
55
- A gateway user
56
- """
57
-
58
- bare_jid: str
59
- """Bare JID of the user"""
60
- registration_form: dict[str, Optional[str]]
61
- """Content of the registration form, as a dict"""
62
- plugin_data: Optional[dict] = None
63
- registration_date: Optional[datetime.datetime] = None
64
-
65
- def __hash__(self):
66
- return hash(self.bare_jid)
67
-
68
- def __repr__(self):
69
- return f"<User {self.bare_jid}>"
70
-
71
- def __post_init__(self):
72
- if self.registration_date is None:
73
- self.registration_date = datetime.datetime.now()
74
-
75
- @property
76
- def jid(self) -> JID:
77
- """
78
- The user's (bare) JID
79
-
80
- :return:
81
- """
82
- return JID(self.bare_jid)
83
-
84
- def get(self, field: str, default: str = "") -> Optional[str]:
85
- # """
86
- # Get fields from the registration form (required to comply with slixmpp backend protocol)
87
- #
88
- # :param field: Name of the field
89
- # :param default: Default value to return if the field is not present
90
- #
91
- # :return: Value of the field
92
- # """
93
- return self.registration_form.get(field, default)
94
-
95
-
96
- class UserStore:
97
- """
98
- Basic user store implementation using shelve from the python standard library
99
-
100
- Set_file must be called before it is usable
101
- """
102
-
103
- def __init__(self):
104
- self._users: shelve.Shelf[GatewayUser] = None # type: ignore
105
-
106
- def set_file(self, filename: PathLike, secret_key: Optional[str] = None):
107
- """
108
- Set the file to use to store user data
109
-
110
- :param filename: Path to the shelf file
111
- :param secret_key: Secret key to store files encrypted on disk
112
- """
113
- if self._users is not None:
114
- raise RuntimeError("Shelf file already set!")
115
- if os.path.exists(filename):
116
- log.info("Using existing slidge DB: %s", filename)
117
- else:
118
- log.info("Creating a new slidge DB: %s", filename)
119
- if secret_key:
120
- self._users = EncryptedShelf(filename, key=secret_key)
121
- else:
122
- self._users = shelve.open(str(filename))
123
- log.info("Registered users in the DB: %s", list(self._users.keys()))
124
-
125
- def get_all(self) -> Iterable[GatewayUser]:
126
- """
127
- Get all users in the store
128
-
129
- :return: An iterable of GatewayUsers
130
- """
131
- return self._users.values()
132
-
133
- def commit(self, user: GatewayUser):
134
- self._users[user.jid.bare] = user
135
- self._users.sync()
136
-
137
- def get(self, _gateway_jid, _node, ifrom: JID, iq) -> Optional[GatewayUser]:
138
- """
139
- Get a user from the store
140
-
141
- NB: there is no reason to call this, it is used by SliXMPP internal API
142
-
143
- :param _gateway_jid:
144
- :param _node:
145
- :param ifrom:
146
- :param iq:
147
- :return:
148
- """
149
- if ifrom is None: # bug in SliXMPP's XEP_0100 plugin
150
- ifrom = iq["from"]
151
- log.debug("Getting user %s", ifrom.bare)
152
- return self._users.get(ifrom.bare)
153
-
154
- def get_by_jid(self, jid: JID) -> Optional[GatewayUser]:
155
- """
156
- Convenience function to get a user from their JID.
157
-
158
- :param jid: JID of the gateway user
159
- :return:
160
- """
161
- return self._users.get(jid.bare)
162
-
163
- def get_by_stanza(self, s: Union[Presence, Message, Iq]) -> Optional[GatewayUser]:
164
- """
165
- Convenience function to get a user from a stanza they sent.
166
-
167
- :param s: A stanza sent by the gateway user
168
- :return:
169
- """
170
- return self.get_by_jid(s.get_from())
171
-
172
- def close(self):
173
- self._users.sync()
174
- self._users.close()
175
-
176
-
177
- user_store = UserStore()
178
- """
179
- A persistent store for slidge users. Not public, but I didn't find how to hide
180
- it from the docs!
181
- """
182
-
183
- log = logging.getLogger(__name__)
@@ -1,7 +0,0 @@
1
- from slixmpp.plugins.base import register_plugin
2
-
3
- from . import stanza
4
- from .privilege import XEP_0356_OLD
5
- from .stanza import PermOld, PrivilegeOld
6
-
7
- register_plugin(XEP_0356_OLD)
@@ -1,167 +0,0 @@
1
- import logging
2
- import typing
3
- from collections import defaultdict
4
-
5
- from slixmpp import JID, Iq, Message
6
- from slixmpp.plugins.base import BasePlugin
7
- from slixmpp.plugins.xep_0356.permissions import (
8
- MessagePermission,
9
- Permissions,
10
- RosterAccess,
11
- )
12
- from slixmpp.types import JidStr
13
- from slixmpp.xmlstream import StanzaBase
14
- from slixmpp.xmlstream.handler import Callback
15
- from slixmpp.xmlstream.matcher import StanzaPath
16
-
17
- from . import stanza
18
-
19
- log = logging.getLogger(__name__)
20
-
21
-
22
- # noinspection PyPep8Naming
23
- class XEP_0356_OLD(BasePlugin):
24
- """
25
- XEP-0356: Privileged Entity
26
-
27
- Events:
28
-
29
- ::
30
-
31
- privileges_advertised_old -- Received message/privilege from the server
32
- """
33
-
34
- name = "xep_0356_old"
35
- description = "XEP-0356: Privileged Entity (slidge - old namespace)"
36
- dependencies = {"xep_0297"}
37
- stanza = stanza
38
-
39
- granted_privileges: defaultdict[JidStr, Permissions] = defaultdict(Permissions)
40
-
41
- def plugin_init(self):
42
- if not self.xmpp.is_component:
43
- log.error("XEP 0356 is only available for components")
44
- return
45
-
46
- stanza.register()
47
-
48
- self.xmpp.register_handler(
49
- Callback(
50
- "Privileges_old",
51
- StanzaPath("message/privilege_old"),
52
- self._handle_privilege,
53
- )
54
- )
55
-
56
- def plugin_end(self):
57
- self.xmpp.remove_handler("Privileges_old")
58
-
59
- def _handle_privilege(self, msg: StanzaBase):
60
- """
61
- Called when the XMPP server advertise the component's privileges.
62
-
63
- Stores the privileges in this instance's granted_privileges attribute (a dict)
64
- and raises the privileges_advertised event
65
- """
66
- for perm in msg["privilege_old"]["perms"]:
67
- setattr(
68
- self.granted_privileges[msg.get_from()], perm["access"], perm["type"]
69
- )
70
- log.debug(f"Privileges (old): {self.granted_privileges}")
71
- self.xmpp.event("privileges_advertised_old")
72
-
73
- def send_privileged_message(self, msg: Message):
74
- if (
75
- self.granted_privileges[msg.get_from().domain].message
76
- != MessagePermission.OUTGOING
77
- ):
78
- raise PermissionError(
79
- "The server hasn't authorized us to send messages on behalf of other users"
80
- )
81
- else:
82
- self._make_privileged_message(msg).send()
83
-
84
- def _make_privileged_message(self, msg: Message):
85
- server = msg.get_from().domain
86
- wrapped = self.xmpp.make_message(mto=server, mfrom=self.xmpp.boundjid.bare)
87
- wrapped["privilege_old"]["forwarded"].append(msg)
88
- return wrapped
89
-
90
- def _make_get_roster(self, jid: typing.Union[JID, str], **iq_kwargs):
91
- return self.xmpp.make_iq_get(
92
- queryxmlns="jabber:iq:roster",
93
- ifrom=self.xmpp.boundjid.bare,
94
- ito=jid,
95
- **iq_kwargs,
96
- )
97
-
98
- def _make_set_roster(
99
- self,
100
- jid: typing.Union[JID, str],
101
- roster_items: dict,
102
- **iq_kwargs,
103
- ):
104
- iq = self.xmpp.make_iq_set(
105
- ifrom=self.xmpp.boundjid.bare,
106
- ito=jid,
107
- **iq_kwargs,
108
- )
109
- iq["roster"]["items"] = roster_items
110
- return iq
111
-
112
- async def get_roster(self, jid: typing.Union[JID, str], **send_kwargs) -> Iq:
113
- """
114
- Return the roster of user on the server the component has privileged access to.
115
-
116
- Raises ValueError if the server did not advertise the corresponding privileges
117
-
118
- :param jid: user we want to fetch the roster from
119
- """
120
- if isinstance(jid, str):
121
- jid = JID(jid)
122
- if self.granted_privileges[jid.domain].roster not in (
123
- RosterAccess.GET,
124
- RosterAccess.BOTH,
125
- ):
126
- raise PermissionError(
127
- "The server did not grant us privileges to get rosters"
128
- )
129
- else:
130
- return await self._make_get_roster(jid).send(**send_kwargs)
131
-
132
- async def set_roster(
133
- self, jid: typing.Union[JID, str], roster_items: dict, **send_kwargs
134
- ) -> Iq:
135
- """
136
- Return the roster of user on the server the component has privileged access to.
137
-
138
- Raises ValueError if the server did not advertise the corresponding privileges
139
-
140
- :param jid: user we want to add or modify roster items
141
- :param roster_items: a dict containing the roster items' JIDs as keys and
142
- nested dicts containing names, subscriptions and groups.
143
- Example:
144
- {
145
- "friend1@example.com": {
146
- "name": "Friend 1",
147
- "subscription": "both",
148
- "groups": ["group1", "group2"],
149
- },
150
- "friend2@example.com": {
151
- "name": "Friend 2",
152
- "subscription": "from",
153
- "groups": ["group3"],
154
- },
155
- }
156
- """
157
- if isinstance(jid, str):
158
- jid = JID(jid)
159
- if self.granted_privileges[jid.domain].roster not in (
160
- RosterAccess.GET,
161
- RosterAccess.BOTH,
162
- ):
163
- raise PermissionError(
164
- "The server did not grant us privileges to set rosters"
165
- )
166
- else:
167
- return await self._make_set_roster(jid, roster_items).send(**send_kwargs)
@@ -1,44 +0,0 @@
1
- from slixmpp.plugins.xep_0297 import Forwarded
2
- from slixmpp.stanza import Message
3
- from slixmpp.xmlstream import ElementBase, register_stanza_plugin
4
-
5
-
6
- class PrivilegeOld(ElementBase):
7
- namespace = "urn:xmpp:privilege:1"
8
- name = "privilege"
9
- plugin_attrib = "privilege_old"
10
-
11
- def permission(self, access):
12
- for perm in self["perms"]:
13
- if perm["access"] == access:
14
- return perm["type"]
15
-
16
- def roster(self):
17
- return self.permission("roster")
18
-
19
- def message(self):
20
- return self.permission("message")
21
-
22
- def presence(self):
23
- return self.permission("presence")
24
-
25
- def add_perm(self, access, type):
26
- # This should only be needed for servers, so maybe out of scope for slixmpp
27
- perm = PermOld()
28
- perm["type"] = type
29
- perm["access"] = access
30
- self.append(perm)
31
-
32
-
33
- class PermOld(ElementBase):
34
- namespace = "urn:xmpp:privilege:1"
35
- name = "perm"
36
- plugin_attrib = "perm"
37
- plugin_multi_attrib = "perms"
38
- interfaces = {"type", "access"}
39
-
40
-
41
- def register():
42
- register_stanza_plugin(Message, PrivilegeOld)
43
- register_stanza_plugin(PrivilegeOld, Forwarded)
44
- register_stanza_plugin(PrivilegeOld, PermOld, iterable=True)
slidge/util/db.py DELETED
@@ -1,5 +0,0 @@
1
- # here to allow migration of the user store from v0.1
2
- # since it relies on shelf, which relies on pickle, we need to keep objects
3
- # importable where they were when the shelf was written
4
-
5
- from ..db.alembic.old_user_store import * # noqa:F403
File without changes