slidge 0.2.5__py3-none-any.whl → 0.2.7__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.
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.5"
5
+ __version__ = "v0.2.7"
slidge/command/adhoc.py CHANGED
@@ -235,18 +235,29 @@ class AdhocProvider:
235
235
  """
236
236
  Get items for a disco query
237
237
 
238
- :param jid: who is requesting the disco
238
+ :param jid: the entity that should return its items
239
239
  :param node: which command node is requested
240
240
  :param iq: the disco query IQ
241
241
  :return: commands accessible to the given JID will be listed
242
242
  """
243
+ ifrom = iq.get_from()
244
+ ifrom_str = str(ifrom)
245
+ if (
246
+ not self.xmpp.jid_validator.match(ifrom_str)
247
+ and ifrom_str not in config.ADMINS
248
+ ):
249
+ raise XMPPError(
250
+ "forbidden",
251
+ "You are not authorized to execute adhoc commands on this gateway. "
252
+ "If this is unexpected, ask your administrator to verify that "
253
+ "'user-jid-validator' is correctly set in slidge's configuration.",
254
+ )
255
+
243
256
  all_items = self.xmpp.plugin["xep_0030"].static.get_items(jid, node, None, None)
244
257
  log.debug("Static items: %r", all_items)
245
258
  if not all_items:
246
259
  return DiscoItems()
247
260
 
248
- ifrom = iq.get_from()
249
-
250
261
  filtered_items = DiscoItems()
251
262
  filtered_items["node"] = self.xmpp.plugin["xep_0050"].stanza.Command.namespace
252
263
  for item in all_items:
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,7 +448,7 @@ 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:
@@ -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
 
slidge/core/gateway.py CHANGED
@@ -515,6 +515,7 @@ class BaseGateway(
515
515
  @timeit
516
516
  async def login_wrap(self, session: "BaseSession"):
517
517
  session.send_gateway_status("Logging in…", show="dnd")
518
+ session.is_logging_in = True
518
519
  try:
519
520
  status = await session.login()
520
521
  except Exception as e:
@@ -808,7 +809,9 @@ class BaseGateway(
808
809
 
809
810
  async def unregister_user(self, user: GatewayUser):
810
811
  self.send_presence(
811
- 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,
812
815
  )
813
816
  await self.xmpp.plugin["xep_0077"].api["user_remove"](None, None, user.jid)
814
817
  await self.xmpp.session_cls.kill_by_jid(user.jid)
@@ -913,7 +916,6 @@ SLIXMPP_PLUGINS = [
913
916
  "xep_0333", # Chat markers
914
917
  "xep_0334", # Message Processing Hints
915
918
  "xep_0356", # Privileged Entity
916
- "xep_0356_old", # Privileged Entity (old namespace)
917
919
  "xep_0363", # HTTP file upload
918
920
  "xep_0385", # Stateless in-line media sharing
919
921
  "xep_0402", # PEP Native Bookmarks
@@ -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.im/docs/slidge/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/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/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
@@ -698,6 +698,8 @@ class MultiStore(EngineMixin):
698
698
  ).scalar()
699
699
  if multi is None:
700
700
  return []
701
+ if multi.legacy_ids_multi is None:
702
+ return []
701
703
  return [m.xmpp_id for m in multi.legacy_ids_multi.xmpp_ids]
702
704
 
703
705
  def set_xmpp_ids(
@@ -1072,6 +1074,7 @@ class ParticipantStore(EngineMixin):
1072
1074
  .where(Participant.id == participant.pk)
1073
1075
  .values(
1074
1076
  resource=participant.jid.resource,
1077
+ nickname_no_illegal=participant._nickname_no_illegal,
1075
1078
  affiliation=participant.affiliation,
1076
1079
  role=participant.role,
1077
1080
  presence_sent=participant._presence_sent, # type:ignore
@@ -71,6 +71,7 @@ class LegacyParticipant(
71
71
  role: MucRole = "participant",
72
72
  affiliation: MucAffiliation = "member",
73
73
  resource: str | None = None,
74
+ nickname_no_illegal: str | None = None,
74
75
  ):
75
76
  self.session = session = muc.session
76
77
  self.xmpp = session.xmpp
@@ -87,7 +88,8 @@ class LegacyParticipant(
87
88
  if resource is None:
88
89
  self.__update_jid(nickname)
89
90
  else:
90
- self._nickname_no_illegal = resource
91
+ assert nickname_no_illegal is not None
92
+ self._nickname_no_illegal = nickname_no_illegal
91
93
  self.jid = JID(self.muc.jid)
92
94
  self.jid.resource = resource
93
95
 
@@ -518,6 +520,7 @@ class LegacyParticipant(
518
520
  role=stored.role,
519
521
  affiliation=stored.affiliation,
520
522
  resource=stored.resource,
523
+ nickname_no_illegal=stored.nickname_no_illegal,
521
524
  )
522
525
  part.pk = stored.id
523
526
  if contact is not None:
slidge/group/room.py CHANGED
@@ -14,6 +14,7 @@ from slixmpp.jid import _unescape_node
14
14
  from slixmpp.plugins.xep_0004 import Form
15
15
  from slixmpp.plugins.xep_0060.stanza import Item
16
16
  from slixmpp.plugins.xep_0082 import parse as str_to_datetime
17
+ from slixmpp.plugins.xep_0469.stanza import NS as PINNING_NS
17
18
  from slixmpp.xmlstream import ET
18
19
 
19
20
  from ..contact.contact import LegacyContact
@@ -26,6 +27,7 @@ from ..core.mixins.disco import ChatterDiscoMixin
26
27
  from ..core.mixins.lock import NamedLockMixin
27
28
  from ..core.mixins.recipient import ReactionRecipientMixin, ThreadRecipientMixin
28
29
  from ..db.models import Room
30
+ from ..slixfix.xep_0492.stanza import NS as NOTIFY_NS
29
31
  from ..slixfix.xep_0492.stanza import WhenLiteral
30
32
  from ..util import ABCSubclassableOnceAtMost
31
33
  from ..util.types import (
@@ -231,7 +233,7 @@ class LegacyMUC(
231
233
  self._participants_filled = True
232
234
  async for p in self.fill_participants():
233
235
  self.__participants_store.update(p)
234
- self.__store.set_participants_filled(self.pk)
236
+ self.__store.set_participants_filled(self.pk)
235
237
 
236
238
  async def get_participants(self) -> AsyncIterator[LegacyParticipant]:
237
239
  assert self.pk is not None
@@ -248,19 +250,21 @@ class LegacyMUC(
248
250
  async with self.lock("fill participants"):
249
251
  self._participants_filled = True
250
252
  # We only fill the participants list if/when the MUC is first
251
- # joined by an XMPP client. But we may have instantiated
253
+ # joined by an XMPP client. But we may have instantiated some before.
252
254
  resources = set[str]()
255
+ async for participant in self.fill_participants():
256
+ # TODO: batch SQL update at the end of this function for perf?
257
+ self.__store_participant(participant)
258
+ yield participant
259
+ resources.add(participant.jid.resource)
253
260
  for db_participant in self.xmpp.store.participants.get_all(
254
261
  self.pk, user_included=True
255
262
  ):
256
263
  participant = self.Participant.from_store(
257
264
  self.session, db_participant, muc=self
258
265
  )
259
- resources.add(participant.jid.resource)
260
- yield participant
261
- async for p in self.fill_participants():
262
- if p.jid.resource not in resources:
263
- yield p
266
+ if participant.jid.resource not in resources:
267
+ yield participant
264
268
  self.__store.set_participants_filled(self.pk)
265
269
  return
266
270
 
@@ -423,7 +427,7 @@ class LegacyMUC(
423
427
 
424
428
  if subject_setter == self._subject_setter:
425
429
  return
426
- assert isinstance(subject_setter, str)
430
+ assert isinstance(subject_setter, str | None)
427
431
  self._subject_setter = subject_setter
428
432
  if self._updating_info:
429
433
  return
@@ -687,6 +691,8 @@ class LegacyMUC(
687
691
  assert self.pk is not None
688
692
  p.pk = self.__participants_store.add(self.pk, p.nickname)
689
693
  self.__participants_store.update(p)
694
+ if p._hats:
695
+ self.__participants_store.set_hats(p.pk, p._hats)
690
696
 
691
697
  async def get_participant(
692
698
  self,
@@ -995,6 +1001,32 @@ class LegacyMUC(
995
1001
  p["muc"]["status_codes"] = {110, 333}
996
1002
  p.send()
997
1003
 
1004
+ async def __get_bookmark(self) -> Item | None:
1005
+ item = Item()
1006
+ item["id"] = self.jid
1007
+
1008
+ iq = Iq(stype="get", sfrom=self.user_jid, sto=self.user_jid)
1009
+ iq["pubsub"]["items"]["node"] = self.xmpp["xep_0402"].stanza.NS
1010
+ iq["pubsub"]["items"].append(item)
1011
+
1012
+ try:
1013
+ ans = await self.xmpp["xep_0356"].send_privileged_iq(iq)
1014
+ if len(ans["pubsub"]["items"]) != 1:
1015
+ return None
1016
+ # this below creates the item if it wasn't here already
1017
+ # (slixmpp annoying magic)
1018
+ item = ans["pubsub"]["items"]["item"]
1019
+ item["id"] = self.jid
1020
+ return item
1021
+ except (IqError, IqTimeout) as exc:
1022
+ warnings.warn(f"Cannot fetch bookmark: {exc}")
1023
+ return None
1024
+ except PermissionError:
1025
+ warnings.warn(
1026
+ "IQ privileges (XEP0356) are not set, we cannot fetch the user bookmarks"
1027
+ )
1028
+ return None
1029
+
998
1030
  async def add_to_bookmarks(
999
1031
  self,
1000
1032
  auto_join=True,
@@ -1026,68 +1058,90 @@ class LegacyMUC(
1026
1058
  If set to ``None`` (default), the setting will be untouched. Only the "global"
1027
1059
  notification setting is supported (ie, per client type is not possible).
1028
1060
  """
1029
- item = Item()
1030
- item["id"] = self.jid
1061
+ existing = await self.__get_bookmark() if preserve else None
1031
1062
 
1032
- iq = Iq(stype="get", sfrom=self.user_jid, sto=self.user_jid)
1033
- iq["pubsub"]["items"]["node"] = self.xmpp["xep_0402"].stanza.NS
1034
- iq["pubsub"]["items"].append(item)
1063
+ new = Item()
1064
+ new["id"] = self.jid
1065
+ new["conference"]["nick"] = self.user_nick
1035
1066
 
1036
- is_update = False
1037
- if preserve:
1038
- try:
1039
- ans = await self.xmpp["xep_0356"].send_privileged_iq(iq)
1040
- is_update = len(ans["pubsub"]["items"]) == 1
1041
- # this below creates the item if it wasn't here already
1042
- # (slixmpp annoying magic)
1043
- item = ans["pubsub"]["items"]["item"]
1044
- item["id"] = self.jid
1045
- except (IqError, IqTimeout):
1046
- item["conference"]["autojoin"] = auto_join
1047
- except PermissionError:
1048
- warnings.warn(
1049
- "IQ privileges (XEP0356) are not set, we cannot fetch the user bookmarks"
1050
- )
1051
- else:
1052
- # if the bookmark is already present, we preserve it as much as
1053
- # possible, especially custom <extensions>
1054
- self.log.debug("Existing: %s", item)
1055
- # if it's an update, we do not touch the auto join flag
1056
- if not is_update:
1057
- item["conference"]["autojoin"] = auto_join
1067
+ if existing is None:
1068
+ change = True
1069
+ new["conference"]["autojoin"] = auto_join
1058
1070
  else:
1059
- item["conference"]["autojoin"] = auto_join
1060
-
1061
- item["conference"]["nick"] = self.user_nick
1071
+ change = False
1072
+ new["conference"]["autojoin"] = existing["conference"]["autojoin"]
1073
+
1074
+ existing_extensions = existing is not None and existing[
1075
+ "conference"
1076
+ ].get_plugin("extensions", check=True)
1077
+
1078
+ # preserving extensions we don't know about is a MUST
1079
+ if existing_extensions:
1080
+ assert existing is not None
1081
+ for el in existing["conference"]["extensions"].xml:
1082
+ if el.tag.startswith(f"{{{NOTIFY_NS}}}"):
1083
+ if notify is not None:
1084
+ continue
1085
+ if el.tag.startswith(f"{{{PINNING_NS}}}"):
1086
+ if pin is not None:
1087
+ continue
1088
+ new["conference"]["extensions"].append(el)
1062
1089
 
1063
1090
  if pin is not None:
1064
- item["conference"]["extensions"]["pinned"] = pin
1091
+ if existing_extensions:
1092
+ assert existing is not None
1093
+ existing_pin = (
1094
+ existing["conference"]["extensions"].get_plugin(
1095
+ "pinned", check=True
1096
+ )
1097
+ is not None
1098
+ )
1099
+ if existing_pin != pin:
1100
+ change = True
1101
+ new["conference"]["extensions"]["pinned"] = pin
1065
1102
 
1066
1103
  if notify is not None:
1067
- item["conference"]["extensions"]["notify"].configure(notify)
1068
-
1069
- iq = Iq(stype="set", sfrom=self.user_jid, sto=self.user_jid)
1070
- iq["pubsub"]["publish"]["node"] = self.xmpp["xep_0402"].stanza.NS
1071
- iq["pubsub"]["publish"].append(item)
1072
-
1073
- iq["pubsub"]["publish_options"] = _BOOKMARKS_OPTIONS
1104
+ new["conference"]["extensions"].enable("notify")
1105
+ if existing_extensions:
1106
+ assert existing is not None
1107
+ existing_notify = existing["conference"]["extensions"].get_plugin(
1108
+ "notify", check=True
1109
+ )
1110
+ if existing_notify is None:
1111
+ change = True
1112
+ else:
1113
+ if existing_notify.get_config() != notify:
1114
+ change = True
1115
+ for el in existing_notify:
1116
+ new["conference"]["extensions"]["notify"].append(el)
1117
+ new["conference"]["extensions"]["notify"].configure(notify)
1118
+
1119
+ if change:
1120
+ iq = Iq(stype="set", sfrom=self.user_jid, sto=self.user_jid)
1121
+ iq["pubsub"]["publish"]["node"] = self.xmpp["xep_0402"].stanza.NS
1122
+ iq["pubsub"]["publish"].append(new)
1123
+
1124
+ iq["pubsub"]["publish_options"] = _BOOKMARKS_OPTIONS
1074
1125
 
1075
- try:
1076
- await self.xmpp["xep_0356"].send_privileged_iq(iq)
1077
- except PermissionError:
1078
- warnings.warn(
1079
- "IQ privileges (XEP0356) are not set, we cannot add bookmarks for the user"
1080
- )
1081
- # fallback by forcing invitation
1082
- invite = True
1083
- except IqError as e:
1084
- warnings.warn(
1085
- f"Something went wrong while trying to set the bookmarks: {e}"
1086
- )
1087
- # fallback by forcing invitation
1088
- invite = True
1126
+ try:
1127
+ await self.xmpp["xep_0356"].send_privileged_iq(iq)
1128
+ except PermissionError:
1129
+ warnings.warn(
1130
+ "IQ privileges (XEP0356) are not set, we cannot add bookmarks for the user"
1131
+ )
1132
+ # fallback by forcing invitation
1133
+ invite = True
1134
+ except IqError as e:
1135
+ warnings.warn(
1136
+ f"Something went wrong while trying to set the bookmarks: {e}"
1137
+ )
1138
+ # fallback by forcing invitation
1139
+ invite = True
1140
+ else:
1141
+ self.log.debug("Bookmark does not need updating.")
1142
+ return
1089
1143
 
1090
- if invite or (config.ALWAYS_INVITE_WHEN_ADDING_BOOKMARKS and not is_update):
1144
+ if invite or (config.ALWAYS_INVITE_WHEN_ADDING_BOOKMARKS and existing is None):
1091
1145
  self.session.send_gateway_invite(
1092
1146
  self, reason="This group could not be added automatically for you"
1093
1147
  )
@@ -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:
@@ -51,7 +51,7 @@ class Notify(ElementBase):
51
51
  self.append(element)
52
52
 
53
53
  def get_config(
54
- self, client_type: Optional[ClientType] = None
54
+ self, client_type: Optional[ClientType] = None
55
55
  ) -> Optional[WhenLiteral]:
56
56
  """
57
57
  Get the chat notification settings for this bookmark.
@@ -67,7 +67,6 @@ class Notify(ElementBase):
67
67
  return cast(WhenLiteral, child.name)
68
68
  return None
69
69
 
70
-
71
70
  class _Base(ElementBase):
72
71
  namespace = NS
73
72
  interfaces = {"client-type"}
@@ -75,14 +74,17 @@ class _Base(ElementBase):
75
74
 
76
75
  class Never(_Base):
77
76
  name = "never"
77
+ plugin_attrib = name
78
78
 
79
79
 
80
80
  class Always(_Base):
81
81
  name = "always"
82
+ plugin_attrib = name
82
83
 
83
84
 
84
85
  class OnMention(_Base):
85
86
  name = "on-mention"
87
+ plugin_attrib = name
86
88
 
87
89
 
88
90
  class Advanced(ElementBase):
@@ -100,3 +102,6 @@ _CLASS_MAP = {
100
102
  def register_plugin():
101
103
  register_stanza_plugin(Extensions, Notify)
102
104
  register_stanza_plugin(Notify, Advanced)
105
+ register_stanza_plugin(Notify, Never, iterable=True)
106
+ register_stanza_plugin(Notify, Always, iterable=True)
107
+ register_stanza_plugin(Notify, OnMention, iterable=True)
slidge/util/util.py CHANGED
@@ -218,18 +218,18 @@ class SlidgeLogger(logging.Logger):
218
218
  log = logging.getLogger(__name__)
219
219
 
220
220
 
221
- def get_version():
221
+ def get_version() -> str:
222
222
  try:
223
223
  git = subprocess.check_output(
224
- ["git", "rev-parse", "HEAD"], stderr=subprocess.DEVNULL
224
+ ["git", "rev-parse", "HEAD"],
225
+ stderr=subprocess.DEVNULL,
226
+ cwd=Path(__file__).parent,
225
227
  ).decode()
226
228
  except (FileNotFoundError, subprocess.CalledProcessError):
227
- pass
229
+ return "NO_VERSION"
228
230
  else:
229
231
  return "git-" + git[:10]
230
232
 
231
- return "NO_VERSION"
232
-
233
233
 
234
234
  def merge_resources(resources: dict[str, ResourceDict]) -> Optional[ResourceDict]:
235
235
  if len(resources) == 0:
@@ -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.5
3
+ Version: 0.2.7
4
4
  Summary: XMPP bridging framework
5
5
  Author-email: Nicolas Cedilnik <nicoco@nicoco.fr>
6
6
  License: GNU AFFERO GENERAL PUBLIC LICENSE
@@ -676,16 +676,15 @@ Classifier: Topic :: Internet :: XMPP
676
676
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
677
677
  Requires-Python: >=3.11
678
678
  Description-Content-Type: text/markdown
679
- Requires-Dist: aiohttp[speedups]>=3.11.11
680
- Requires-Dist: alembic>=1.14.0
681
- 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
682
682
  Requires-Dist: defusedxml>=0.7.1
683
- Requires-Dist: pickle-secure>=0.99.9
684
- Requires-Dist: pillow>=11.0.0
685
- Requires-Dist: python-magic>=0.4.27
686
- Requires-Dist: qrcode>=8.0
687
- Requires-Dist: slixmpp>=1.8.6
688
- 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
689
688
  Requires-Dist: thumbhash>=0.1.2
690
689
 
691
690
  ![Slidge logo](https://codeberg.org/slidge/slidge/raw/branch/main/dev/assets/slidge-color-small.png)
@@ -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=4bAi8qOyNOCy78XyBLZqa2BfhBAl80d4H4U1rf4i8-k,165
3
+ slidge/__version__.py,sha256=4G6eK4xrrezi8g1TxDqa6IID1uzOe-SonV8sT5-MrUs,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=bYLXcYJjbqPE9nBhrEQevqYFh9AS7Ei3m1vTZBIggrc,10557
9
9
  slidge/command/admin.py,sha256=TYrzgCIhjcTIwl1IUaFlUd3D98SPyao10gB20zo8b3Q,6187
10
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=SiFeDoI1zuGKhVoK8O_Jm1ch9jwOsRINKgt1gijrmcc,23121
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=9oIrXYB4Fx9uVQkMYDswrd0xr0ieEH8r1EkGym-AxBE,8060
20
- slidge/core/gateway.py,sha256=S2fLp_KLPyoixlTQzvlrlus55dHFTYp-WkLZNSWw9mE,37022
19
+ slidge/core/config.py,sha256=OjJfpXJaDhMxRB-vYA0cqkSf0fwMt-HMThM8GS1htCg,7964
20
+ slidge/core/gateway.py,sha256=qdvLGh5GF823mACVw5j1WLYMzsSmeGpmQFuWvz5Z9AU,37028
21
21
  slidge/core/pubsub.py,sha256=BoeYE__ptmRAn4x55Hn_6JWRA4nM-XJgDemG5Cy5kN4,11959
22
- slidge/core/session.py,sha256=nQexpCd1jlHOhQPnFI4ri-5odp3N2pU5HO4l7WFetZY,28148
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
@@ -46,7 +46,7 @@ slidge/core/mixins/base.py,sha256=MOd-pas38_52VawQVlxWtBtmTKC6My9G0ZaCeQxOJbs,74
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=0dPpDQwkdAdirUodKpb9Ad7mkr5meWc7S3lf0sXPYnc,7994
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
@@ -54,12 +54,12 @@ slidge/core/mixins/recipient.py,sha256=b0uFnpym-hOFgYxGjXT1xQcZ4YRbDSBftPcNWLzSw
54
54
  slidge/db/__init__.py,sha256=EBDH1JSEhgqYcli2Bw11CRC749wJk8AOucgBzmhDSvU,105
55
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=A2F8QJ88INMSdw0S8G5wDAQJFjwpeExpLuwHu8T3ZHU,46904
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=tGGcAkdcK6aob1YP8e9ODbdK2_dGmFNH0F2x-gHyc4M,17611
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=IilOT_Z5P-gh0lo5KLFc054z6sOrxJQ6MvjPmLKXiRs,49011
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
- slidge/slixfix/xep_0492/stanza.py,sha256=gL0ixpV07Q9eqTXIOjdLVPtim45izuwaLk0iCoZ8e7c,2649
103
+ slidge/slixfix/xep_0492/stanza.py,sha256=TlwyAHozA6zu32QoBb6M12xqWR-ytT0F9XVFqZkd4d4,2895
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.5.dist-info/METADATA,sha256=qPrPddCJQeL3747-F2abvx04v1bGPzyFJiLHX8Xjx0E,44856
115
- slidge-0.2.5.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
116
- slidge-0.2.5.dist-info/entry_points.txt,sha256=py3_x834fFJ2TEzPd18Wt2DnysdAfuVqJ5zzBrXbAZs,44
117
- slidge-0.2.5.dist-info/top_level.txt,sha256=2LRjDYHaGZ5ieCMF8xy58JIiabRMzX-MGMbCZwfE17c,7
118
- slidge-0.2.5.dist-info/RECORD,,
109
+ slidge/util/util.py,sha256=IfyYLkujW6Pk0kvHpfkpQwejFDHSe5oR4_bczEUnhjs,9678
110
+ slidge-0.2.7.dist-info/METADATA,sha256=u5B3zAw9h1inyIcHrGvr23-GNtI7Ba4gHNXLRmTtktc,44841
111
+ slidge-0.2.7.dist-info/WHEEL,sha256=nn6H5-ilmfVryoAQl3ZQ2l8SH5imPWFpm1A5FgEuFV4,91
112
+ slidge-0.2.7.dist-info/entry_points.txt,sha256=py3_x834fFJ2TEzPd18Wt2DnysdAfuVqJ5zzBrXbAZs,44
113
+ slidge-0.2.7.dist-info/top_level.txt,sha256=2LRjDYHaGZ5ieCMF8xy58JIiabRMzX-MGMbCZwfE17c,7
114
+ slidge-0.2.7.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (75.8.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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