slidge 0.2.12__py3-none-any.whl → 0.3.0a0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. slidge/__init__.py +5 -2
  2. slidge/command/adhoc.py +9 -3
  3. slidge/command/admin.py +16 -12
  4. slidge/command/base.py +16 -12
  5. slidge/command/chat_command.py +25 -16
  6. slidge/command/user.py +7 -8
  7. slidge/contact/contact.py +119 -209
  8. slidge/contact/roster.py +106 -105
  9. slidge/core/config.py +2 -43
  10. slidge/core/dispatcher/caps.py +9 -2
  11. slidge/core/dispatcher/disco.py +13 -3
  12. slidge/core/dispatcher/message/__init__.py +1 -1
  13. slidge/core/dispatcher/message/chat_state.py +17 -8
  14. slidge/core/dispatcher/message/marker.py +7 -5
  15. slidge/core/dispatcher/message/message.py +117 -92
  16. slidge/core/dispatcher/muc/__init__.py +1 -1
  17. slidge/core/dispatcher/muc/admin.py +4 -4
  18. slidge/core/dispatcher/muc/mam.py +10 -6
  19. slidge/core/dispatcher/muc/misc.py +4 -2
  20. slidge/core/dispatcher/muc/owner.py +5 -3
  21. slidge/core/dispatcher/muc/ping.py +3 -1
  22. slidge/core/dispatcher/presence.py +21 -15
  23. slidge/core/dispatcher/registration.py +20 -12
  24. slidge/core/dispatcher/search.py +7 -3
  25. slidge/core/dispatcher/session_dispatcher.py +13 -5
  26. slidge/core/dispatcher/util.py +37 -27
  27. slidge/core/dispatcher/vcard.py +7 -4
  28. slidge/core/gateway.py +168 -84
  29. slidge/core/mixins/__init__.py +1 -11
  30. slidge/core/mixins/attachment.py +163 -148
  31. slidge/core/mixins/avatar.py +100 -177
  32. slidge/core/mixins/db.py +50 -2
  33. slidge/core/mixins/message.py +19 -17
  34. slidge/core/mixins/message_maker.py +29 -15
  35. slidge/core/mixins/message_text.py +38 -30
  36. slidge/core/mixins/presence.py +91 -35
  37. slidge/core/pubsub.py +42 -47
  38. slidge/core/session.py +88 -57
  39. slidge/db/alembic/versions/0337c90c0b96_unify_legacy_xmpp_id_mappings.py +183 -0
  40. slidge/db/alembic/versions/4dbd23a3f868_new_avatar_store.py +56 -0
  41. slidge/db/alembic/versions/54ce3cde350c_use_hash_for_avatar_filenames.py +50 -0
  42. slidge/db/alembic/versions/58b98dacf819_refactor.py +118 -0
  43. slidge/db/alembic/versions/75a62b74b239_ditch_hats_table.py +74 -0
  44. slidge/db/avatar.py +150 -119
  45. slidge/db/meta.py +33 -22
  46. slidge/db/models.py +68 -117
  47. slidge/db/store.py +412 -1094
  48. slidge/group/archive.py +61 -54
  49. slidge/group/bookmarks.py +74 -55
  50. slidge/group/participant.py +135 -142
  51. slidge/group/room.py +315 -312
  52. slidge/main.py +28 -18
  53. slidge/migration.py +2 -12
  54. slidge/slixfix/__init__.py +20 -4
  55. slidge/slixfix/delivery_receipt.py +6 -4
  56. slidge/slixfix/link_preview/link_preview.py +1 -1
  57. slidge/slixfix/link_preview/stanza.py +1 -1
  58. slidge/slixfix/roster.py +5 -7
  59. slidge/slixfix/xep_0077/register.py +8 -8
  60. slidge/slixfix/xep_0077/stanza.py +7 -7
  61. slidge/slixfix/xep_0100/gateway.py +12 -13
  62. slidge/slixfix/xep_0153/vcard_avatar.py +1 -1
  63. slidge/slixfix/xep_0292/vcard4.py +1 -1
  64. slidge/util/archive_msg.py +11 -5
  65. slidge/util/conf.py +23 -20
  66. slidge/util/jid_escaping.py +1 -1
  67. slidge/{core/mixins → util}/lock.py +6 -6
  68. slidge/util/test.py +30 -29
  69. slidge/util/types.py +22 -18
  70. slidge/util/util.py +19 -22
  71. {slidge-0.2.12.dist-info → slidge-0.3.0a0.dist-info}/METADATA +1 -1
  72. slidge-0.3.0a0.dist-info/RECORD +117 -0
  73. {slidge-0.2.12.dist-info → slidge-0.3.0a0.dist-info}/WHEEL +1 -1
  74. slidge-0.2.12.dist-info/RECORD +0 -112
  75. {slidge-0.2.12.dist-info → slidge-0.3.0a0.dist-info}/entry_points.txt +0 -0
  76. {slidge-0.2.12.dist-info → slidge-0.3.0a0.dist-info}/licenses/LICENSE +0 -0
  77. {slidge-0.2.12.dist-info → slidge-0.3.0a0.dist-info}/top_level.txt +0 -0
slidge/group/archive.py CHANGED
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, Collection, Optional
6
6
 
7
7
  from slixmpp import Iq, Message
8
8
 
9
- from ..db.models import ArchivedMessage, ArchivedMessageSource
9
+ from ..db.models import ArchivedMessage, ArchivedMessageSource, Room
10
10
  from ..db.store import MAMStore
11
11
  from ..util.archive_msg import HistoryMessage
12
12
  from ..util.types import HoleBound
@@ -16,17 +16,17 @@ if TYPE_CHECKING:
16
16
 
17
17
 
18
18
  class MessageArchive:
19
- def __init__(self, room_pk: int, store: MAMStore):
20
- self.room_pk = room_pk
19
+ def __init__(self, room: Room, store: MAMStore) -> None:
20
+ self.room = room
21
21
  self.__store = store
22
22
 
23
23
  def add(
24
24
  self,
25
25
  msg: Message,
26
26
  participant: Optional["LegacyParticipant"] = None,
27
- archive_only=False,
27
+ archive_only: bool = False,
28
28
  legacy_msg_id=None,
29
- ):
29
+ ) -> None:
30
30
  """
31
31
  Add a message to the archive if it is deemed archivable
32
32
 
@@ -39,8 +39,8 @@ class MessageArchive:
39
39
  return
40
40
  new_msg = copy(msg)
41
41
  if participant and not participant.muc.is_anonymous:
42
- new_msg["muc"]["role"] = participant.role
43
- new_msg["muc"]["affiliation"] = participant.affiliation
42
+ new_msg["muc"]["role"] = participant.role or "participant"
43
+ new_msg["muc"]["affiliation"] = participant.affiliation or "member"
44
44
  if participant.contact:
45
45
  new_msg["muc"]["jid"] = participant.contact.jid.bare
46
46
  elif participant.is_user:
@@ -53,12 +53,15 @@ class MessageArchive:
53
53
  f"{uuid.uuid4()}@{participant.xmpp.boundjid.bare}"
54
54
  )
55
55
 
56
- self.__store.add_message(
57
- self.room_pk,
58
- HistoryMessage(new_msg),
59
- archive_only,
60
- None if legacy_msg_id is None else str(legacy_msg_id),
61
- )
56
+ with self.__store.session() as orm:
57
+ self.__store.add_message(
58
+ orm,
59
+ self.room.id,
60
+ HistoryMessage(new_msg),
61
+ archive_only,
62
+ None if legacy_msg_id is None else str(legacy_msg_id),
63
+ )
64
+ orm.commit()
62
65
 
63
66
  def __iter__(self):
64
67
  return iter(self.get_all())
@@ -71,31 +74,32 @@ class MessageArchive:
71
74
  )
72
75
 
73
76
  def get_hole_bounds(self) -> tuple[HoleBound | None, HoleBound | None]:
74
- most_recent = self.__store.get_most_recent_with_legacy_id(self.room_pk)
75
- if most_recent is None:
76
- return None, None
77
- if most_recent.source == ArchivedMessageSource.BACKFILL:
78
- # most recent = only backfill, fetch everything since last backfill
79
- return self.__to_bound(most_recent), None
80
-
81
- most_recent_back_filled = self.__store.get_most_recent_with_legacy_id(
82
- self.room_pk, ArchivedMessageSource.BACKFILL
83
- )
84
- if most_recent_back_filled is None:
85
- # group was never back-filled, fetch everything before first live
86
- least_recent_live = self.__store.get_first(self.room_pk, True)
77
+ with self.__store.session() as orm:
78
+ most_recent = self.__store.get_most_recent_with_legacy_id(orm, self.room.id)
79
+ if most_recent is None:
80
+ return None, None
81
+ if most_recent.source == ArchivedMessageSource.BACKFILL:
82
+ # most recent = only backfill, fetch everything since last backfill
83
+ return self.__to_bound(most_recent), None
84
+
85
+ most_recent_back_filled = self.__store.get_most_recent_with_legacy_id(
86
+ orm, self.room.id, ArchivedMessageSource.BACKFILL
87
+ )
88
+ if most_recent_back_filled is None:
89
+ # group was never back-filled, fetch everything before first live
90
+ least_recent_live = self.__store.get_first(orm, self.room.id, True)
91
+ assert least_recent_live is not None
92
+ return None, self.__to_bound(least_recent_live)
93
+
94
+ assert most_recent_back_filled.legacy_id is not None
95
+ least_recent_live = self.__store.get_least_recent_with_legacy_id_after(
96
+ orm, self.room.id, most_recent_back_filled.legacy_id
97
+ )
87
98
  assert least_recent_live is not None
88
- return None, self.__to_bound(least_recent_live)
89
-
90
- assert most_recent_back_filled.legacy_id is not None
91
- least_recent_live = self.__store.get_least_recent_with_legacy_id_after(
92
- self.room_pk, most_recent_back_filled.legacy_id
93
- )
94
- assert least_recent_live is not None
95
- # this is a hole caused by slidge downtime
96
- return self.__to_bound(most_recent_back_filled), self.__to_bound(
97
- least_recent_live
98
- )
99
+ # this is a hole caused by slidge downtime
100
+ return self.__to_bound(most_recent_back_filled), self.__to_bound(
101
+ least_recent_live
102
+ )
99
103
 
100
104
  def get_all(
101
105
  self,
@@ -106,22 +110,24 @@ class MessageArchive:
106
110
  ids: Collection[str] = (),
107
111
  last_page_n: Optional[int] = None,
108
112
  sender: Optional[str] = None,
109
- flip=False,
113
+ flip: bool = False,
110
114
  ):
111
- for msg in self.__store.get_messages(
112
- self.room_pk,
113
- before_id=before_id,
114
- after_id=after_id,
115
- ids=ids,
116
- last_page_n=last_page_n,
117
- sender=sender,
118
- start_date=start_date,
119
- end_date=end_date,
120
- flip=flip,
121
- ):
122
- yield msg
123
-
124
- async def send_metadata(self, iq: Iq):
115
+ with self.__store.session() as orm:
116
+ for msg in self.__store.get_messages(
117
+ orm,
118
+ self.room.id,
119
+ before_id=before_id,
120
+ after_id=after_id,
121
+ ids=ids,
122
+ last_page_n=last_page_n,
123
+ sender=sender,
124
+ start_date=start_date,
125
+ end_date=end_date,
126
+ flip=flip,
127
+ ):
128
+ yield msg
129
+
130
+ async def send_metadata(self, iq: Iq) -> None:
125
131
  """
126
132
  Send archive extent, as per the spec
127
133
 
@@ -129,7 +135,8 @@ class MessageArchive:
129
135
  :return:
130
136
  """
131
137
  reply = iq.reply()
132
- messages = self.__store.get_first_and_last(self.room_pk)
138
+ with self.__store.session() as orm:
139
+ messages = self.__store.get_first_and_last(orm, self.room.id)
133
140
  if messages:
134
141
  for x, m in [("start", messages[0]), ("end", messages[-1])]:
135
142
  reply["mam_metadata"][x]["id"] = m.id
@@ -141,7 +148,7 @@ class MessageArchive:
141
148
  reply.send()
142
149
 
143
150
 
144
- def archivable(msg: Message):
151
+ def archivable(msg: Message) -> bool:
145
152
  """
146
153
  Determine if a message stanza is worth archiving, ie, convey meaningful
147
154
  info
slidge/group/bookmarks.py CHANGED
@@ -5,12 +5,11 @@ from typing import TYPE_CHECKING, Generic, Iterator, Optional, Type
5
5
  from slixmpp import JID
6
6
  from slixmpp.exceptions import XMPPError
7
7
 
8
- from ..core.mixins.lock import NamedLockMixin
9
8
  from ..db.models import Room
10
9
  from ..util import SubclassableOnce
11
10
  from ..util.jid_escaping import ESCAPE_TABLE, unescape_node
11
+ from ..util.lock import NamedLockMixin
12
12
  from ..util.types import LegacyGroupIdType, LegacyMUCType
13
- from .archive import MessageArchive
14
13
  from .room import LegacyMUC
15
14
 
16
15
  if TYPE_CHECKING:
@@ -26,13 +25,12 @@ class LegacyBookmarks(
26
25
  This is instantiated once per :class:`~slidge.BaseSession`
27
26
  """
28
27
 
29
- def __init__(self, session: "BaseSession"):
28
+ _muc_cls: Type[LegacyMUCType]
29
+
30
+ def __init__(self, session: "BaseSession") -> None:
30
31
  self.session = session
31
32
  self.xmpp = session.xmpp
32
33
  self.user_jid = session.user_jid
33
- self.__store = self.xmpp.store.rooms
34
-
35
- self._muc_class: Type[LegacyMUCType] = LegacyMUC.get_self_or_unique_subclass()
36
34
 
37
35
  self._user_nick: str = self.session.user_jid.node
38
36
 
@@ -47,23 +45,28 @@ class LegacyBookmarks(
47
45
  return self._user_nick
48
46
 
49
47
  @user_nick.setter
50
- def user_nick(self, nick: str):
48
+ def user_nick(self, nick: str) -> None:
51
49
  self._user_nick = nick
52
50
 
53
- def __iter__(self) -> Iterator[LegacyMUCType]:
54
- for stored in self.__store.get_all(user_pk=self.session.user_pk):
55
- yield self._muc_class.from_store(self.session, stored)
51
+ def from_store(self, stored: Room) -> LegacyMUCType:
52
+ return self._muc_cls(self.session, stored)
56
53
 
57
- def __repr__(self):
54
+ def __iter__(self) -> Iterator[LegacyMUC]:
55
+ with self.xmpp.store.session() as orm:
56
+ for stored in orm.query(Room).filter_by(user=self.session.user).all():
57
+ if stored.updated:
58
+ yield self.from_store(stored)
59
+
60
+ def __repr__(self) -> str:
58
61
  return f"<Bookmarks of {self.user_jid}>"
59
62
 
60
- async def legacy_id_to_jid_local_part(self, legacy_id: LegacyGroupIdType):
63
+ async def legacy_id_to_jid_local_part(self, legacy_id: LegacyGroupIdType) -> str:
61
64
  return await self.legacy_id_to_jid_username(legacy_id)
62
65
 
63
- async def jid_local_part_to_legacy_id(self, local_part: str):
66
+ async def jid_local_part_to_legacy_id(self, local_part: str) -> LegacyGroupIdType:
64
67
  return await self.jid_username_to_legacy_id(local_part)
65
68
 
66
- async def legacy_id_to_jid_username(self, legacy_id: LegacyGroupIdType):
69
+ async def legacy_id_to_jid_username(self, legacy_id: LegacyGroupIdType) -> str:
67
70
  """
68
71
  The default implementation calls ``str()`` on the legacy_id and
69
72
  escape characters according to :xep:`0106`.
@@ -88,21 +91,32 @@ class LegacyBookmarks(
88
91
  if jid.resource:
89
92
  jid = JID(jid.bare)
90
93
  async with self.lock(("bare", jid.bare)):
91
- assert isinstance(jid.user, str)
92
- legacy_id = await self.jid_local_part_to_legacy_id(jid.user)
94
+ legacy_id = await self.jid_local_part_to_legacy_id(jid.node)
93
95
  if self.get_lock(("legacy_id", legacy_id)):
94
- self.log.debug("Not instantiating %s after all", jid)
96
+ self.session.log.debug("Already updating %s via by_legacy_id()", jid)
95
97
  return await self.by_legacy_id(legacy_id)
96
98
 
97
- with self.__store.session():
98
- stored = self.__store.get_by_jid(self.session.user_pk, jid)
99
- return await self.__update_muc(stored, legacy_id, jid)
99
+ with self.session.xmpp.store.session() as orm:
100
+ stored = (
101
+ orm.query(Room)
102
+ .filter_by(user_account_id=self.session.user_pk, jid=jid)
103
+ .one_or_none()
104
+ )
105
+ if stored is None:
106
+ stored = Room(
107
+ user_account_id=self.session.user_pk,
108
+ jid=jid,
109
+ legacy_id=legacy_id,
110
+ )
111
+ return await self.__update_if_needed(stored)
100
112
 
101
113
  def by_jid_only_if_exists(self, jid: JID) -> Optional[LegacyMUCType]:
102
- with self.__store.session():
103
- stored = self.__store.get_by_jid(self.session.user_pk, jid)
114
+ with self.xmpp.store.session() as orm:
115
+ stored = (
116
+ orm.query(Room).filter_by(user=self.session.user, jid=jid).one_or_none()
117
+ )
104
118
  if stored is not None and stored.updated:
105
- return self._muc_class.from_store(self.session, stored)
119
+ return self.from_store(stored)
106
120
  return None
107
121
 
108
122
  async def by_legacy_id(self, legacy_id: LegacyGroupIdType) -> LegacyMUCType:
@@ -110,37 +124,41 @@ class LegacyBookmarks(
110
124
  local = await self.legacy_id_to_jid_local_part(legacy_id)
111
125
  jid = JID(f"{local}@{self.xmpp.boundjid}")
112
126
  if self.get_lock(("bare", jid.bare)):
113
- self.log.debug("Not instantiating %s after all", legacy_id)
127
+ self.session.log.debug("Already updating %s via by_jid()", jid)
114
128
  return await self.by_jid(jid)
115
129
 
116
- with self.__store.session():
117
- stored = self.__store.get_by_legacy_id(
118
- self.session.user_pk, str(legacy_id)
130
+ with self.xmpp.store.session() as orm:
131
+ stored = (
132
+ orm.query(Room)
133
+ .filter_by(
134
+ user_account_id=self.session.user_pk,
135
+ legacy_id=str(legacy_id),
136
+ )
137
+ .one_or_none()
119
138
  )
120
- return await self.__update_muc(stored, legacy_id, jid)
121
-
122
- async def __update_muc(
123
- self, stored: Room | None, legacy_id: LegacyGroupIdType, jid: JID
124
- ):
125
- if stored is None:
126
- muc = self._muc_class(self.session, legacy_id=legacy_id, jid=jid)
127
- else:
128
- muc = self._muc_class.from_store(self.session, stored)
129
- if stored.updated:
130
- return muc
131
-
132
- try:
133
- with muc.updating_info():
134
- await muc.avatar_wrap_update_info()
135
- except XMPPError:
136
- raise
137
- except Exception as e:
138
- raise XMPPError("internal-server-error", str(e))
139
- if not muc.user_nick:
140
- muc.user_nick = self._user_nick
141
- self.log.debug("MUC created: %r", muc)
142
- muc.pk = self.__store.update(muc)
143
- muc.archive = MessageArchive(muc.pk, self.xmpp.store.mam)
139
+ if stored is None:
140
+ stored = Room(
141
+ user_account_id=self.session.user_pk,
142
+ jid=jid,
143
+ legacy_id=legacy_id,
144
+ )
145
+ return await self.__update_if_needed(stored)
146
+
147
+ async def __update_if_needed(self, stored: Room) -> LegacyMUCType:
148
+ muc = self.from_store(stored)
149
+ if muc.stored.updated:
150
+ return muc
151
+
152
+ with muc.updating_info():
153
+ try:
154
+ await muc.update_info()
155
+ except NotImplementedError:
156
+ pass
157
+ except XMPPError:
158
+ raise
159
+ except Exception as e:
160
+ raise XMPPError("internal-server-error", str(e))
161
+
144
162
  return muc
145
163
 
146
164
  @abc.abstractmethod
@@ -164,8 +182,8 @@ class LegacyBookmarks(
164
182
  async def remove(
165
183
  self,
166
184
  muc: LegacyMUC,
167
- reason="You left this group from the official client.",
168
- kick=True,
185
+ reason: str = "You left this group from the official client.",
186
+ kick: bool = True,
169
187
  ) -> None:
170
188
  """
171
189
  Delete everything about a specific group.
@@ -179,8 +197,9 @@ class LegacyBookmarks(
179
197
  to False in case you do this somewhere else in your code, eg, on
180
198
  receiving the confirmation that the group was deleted.
181
199
  """
182
- assert muc.pk is not None
183
200
  if kick:
184
201
  user_participant = await muc.get_user_participant()
185
202
  user_participant.kick(reason)
186
- self.__store.delete(muc.pk)
203
+ with self.xmpp.store.session() as orm:
204
+ orm.delete(muc.stored)
205
+ orm.commit()