slidge 0.2.12__py3-none-any.whl → 0.3.0__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 (93) 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 +123 -210
  8. slidge/contact/roster.py +108 -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 +120 -93
  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 +26 -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 +177 -87
  29. slidge/core/mixins/__init__.py +1 -11
  30. slidge/core/mixins/attachment.py +200 -147
  31. slidge/core/mixins/avatar.py +105 -177
  32. slidge/core/mixins/base.py +3 -1
  33. slidge/core/mixins/db.py +50 -2
  34. slidge/core/mixins/disco.py +1 -1
  35. slidge/core/mixins/message.py +19 -17
  36. slidge/core/mixins/message_maker.py +29 -15
  37. slidge/core/mixins/message_text.py +67 -30
  38. slidge/core/mixins/presence.py +94 -37
  39. slidge/core/pubsub.py +42 -47
  40. slidge/core/session.py +95 -60
  41. slidge/db/alembic/versions/cef02a8b1451_initial_schema.py +361 -0
  42. slidge/db/avatar.py +150 -119
  43. slidge/db/meta.py +33 -22
  44. slidge/db/models.py +69 -117
  45. slidge/db/store.py +414 -1094
  46. slidge/group/archive.py +65 -55
  47. slidge/group/bookmarks.py +96 -59
  48. slidge/group/participant.py +150 -144
  49. slidge/group/room.py +345 -327
  50. slidge/main.py +34 -22
  51. slidge/migration.py +17 -29
  52. slidge/slixfix/__init__.py +20 -4
  53. slidge/slixfix/delivery_receipt.py +6 -4
  54. slidge/slixfix/link_preview/link_preview.py +1 -1
  55. slidge/slixfix/link_preview/stanza.py +1 -1
  56. slidge/slixfix/roster.py +5 -7
  57. slidge/slixfix/xep_0077/register.py +8 -8
  58. slidge/slixfix/xep_0077/stanza.py +7 -7
  59. slidge/slixfix/xep_0100/gateway.py +12 -13
  60. slidge/slixfix/xep_0153/vcard_avatar.py +1 -1
  61. slidge/slixfix/xep_0292/vcard4.py +12 -2
  62. slidge/util/archive_msg.py +11 -5
  63. slidge/util/conf.py +27 -21
  64. slidge/util/jid_escaping.py +1 -1
  65. slidge/{core/mixins → util}/lock.py +6 -6
  66. slidge/util/test.py +30 -29
  67. slidge/util/types.py +24 -18
  68. slidge/util/util.py +26 -22
  69. {slidge-0.2.12.dist-info → slidge-0.3.0.dist-info}/METADATA +1 -1
  70. slidge-0.3.0.dist-info/RECORD +95 -0
  71. {slidge-0.2.12.dist-info → slidge-0.3.0.dist-info}/WHEEL +1 -1
  72. slidge/db/alembic/versions/04cf35e3cf85_add_participant_nickname_no_illegal.py +0 -33
  73. slidge/db/alembic/versions/09f27f098baa_add_missing_attributes_in_room.py +0 -36
  74. slidge/db/alembic/versions/15b0bd83407a_remove_bogus_unique_constraints_on_room_.py +0 -85
  75. slidge/db/alembic/versions/2461390c0af2_store_contacts_caps_verstring_in_db.py +0 -36
  76. slidge/db/alembic/versions/29f5280c61aa_store_subject_setter_in_room.py +0 -37
  77. slidge/db/alembic/versions/2b1f45ab7379_store_room_subject_setter_by_nickname.py +0 -41
  78. slidge/db/alembic/versions/3071e0fa69d4_add_contact_client_type.py +0 -52
  79. slidge/db/alembic/versions/45c24cc73c91_add_bob.py +0 -42
  80. slidge/db/alembic/versions/5bd48bfdffa2_lift_room_legacy_id_constraint.py +0 -61
  81. slidge/db/alembic/versions/82a4af84b679_add_muc_history_filled.py +0 -48
  82. slidge/db/alembic/versions/8b993243a536_add_vcard_content_to_contact_table.py +0 -43
  83. slidge/db/alembic/versions/8d2ced764698_rely_on_db_to_store_contacts_rooms_and_.py +0 -139
  84. slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +0 -50
  85. slidge/db/alembic/versions/abba1ae0edb3_store_avatar_legacy_id_in_the_contact_.py +0 -79
  86. slidge/db/alembic/versions/b33993e87db3_move_everything_to_persistent_db.py +0 -214
  87. slidge/db/alembic/versions/b64b1a793483_add_source_and_legacy_id_for_archived_.py +0 -52
  88. slidge/db/alembic/versions/c4a8ec35a0e8_per_room_user_nick.py +0 -34
  89. slidge/db/alembic/versions/e91195719c2c_store_users_avatars_persistently.py +0 -26
  90. slidge-0.2.12.dist-info/RECORD +0 -112
  91. {slidge-0.2.12.dist-info → slidge-0.3.0.dist-info}/entry_points.txt +0 -0
  92. {slidge-0.2.12.dist-info → slidge-0.3.0.dist-info}/licenses/LICENSE +0 -0
  93. {slidge-0.2.12.dist-info → slidge-0.3.0.dist-info}/top_level.txt +0 -0
slidge/group/archive.py CHANGED
@@ -1,12 +1,13 @@
1
1
  import logging
2
2
  import uuid
3
+ import warnings
3
4
  from copy import copy
4
5
  from datetime import datetime, timezone
5
6
  from typing import TYPE_CHECKING, Collection, Optional
6
7
 
7
8
  from slixmpp import Iq, Message
8
9
 
9
- from ..db.models import ArchivedMessage, ArchivedMessageSource
10
+ from ..db.models import ArchivedMessage, ArchivedMessageSource, Room
10
11
  from ..db.store import MAMStore
11
12
  from ..util.archive_msg import HistoryMessage
12
13
  from ..util.types import HoleBound
@@ -16,17 +17,17 @@ if TYPE_CHECKING:
16
17
 
17
18
 
18
19
  class MessageArchive:
19
- def __init__(self, room_pk: int, store: MAMStore):
20
- self.room_pk = room_pk
20
+ def __init__(self, room: Room, store: MAMStore) -> None:
21
+ self.room = room
21
22
  self.__store = store
22
23
 
23
24
  def add(
24
25
  self,
25
26
  msg: Message,
26
27
  participant: Optional["LegacyParticipant"] = None,
27
- archive_only=False,
28
+ archive_only: bool = False,
28
29
  legacy_msg_id=None,
29
- ):
30
+ ) -> None:
30
31
  """
31
32
  Add a message to the archive if it is deemed archivable
32
33
 
@@ -39,8 +40,8 @@ class MessageArchive:
39
40
  return
40
41
  new_msg = copy(msg)
41
42
  if participant and not participant.muc.is_anonymous:
42
- new_msg["muc"]["role"] = participant.role
43
- new_msg["muc"]["affiliation"] = participant.affiliation
43
+ new_msg["muc"]["role"] = participant.role or "participant"
44
+ new_msg["muc"]["affiliation"] = participant.affiliation or "member"
44
45
  if participant.contact:
45
46
  new_msg["muc"]["jid"] = participant.contact.jid.bare
46
47
  elif participant.is_user:
@@ -48,17 +49,22 @@ class MessageArchive:
48
49
  elif participant.is_system:
49
50
  new_msg["muc"]["jid"] = participant.muc.jid
50
51
  else:
51
- log.warning("No real JID for participant in this group")
52
+ warnings.warn(
53
+ f"No real JID for participant '{participant.nickname}' in '{self.room.name}'"
54
+ )
52
55
  new_msg["muc"]["jid"] = (
53
56
  f"{uuid.uuid4()}@{participant.xmpp.boundjid.bare}"
54
57
  )
55
58
 
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
- )
59
+ with self.__store.session() as orm:
60
+ self.__store.add_message(
61
+ orm,
62
+ self.room.id,
63
+ HistoryMessage(new_msg),
64
+ archive_only,
65
+ None if legacy_msg_id is None else str(legacy_msg_id),
66
+ )
67
+ orm.commit()
62
68
 
63
69
  def __iter__(self):
64
70
  return iter(self.get_all())
@@ -71,31 +77,32 @@ class MessageArchive:
71
77
  )
72
78
 
73
79
  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)
80
+ with self.__store.session() as orm:
81
+ most_recent = self.__store.get_most_recent_with_legacy_id(orm, self.room.id)
82
+ if most_recent is None:
83
+ return None, None
84
+ if most_recent.source == ArchivedMessageSource.BACKFILL:
85
+ # most recent = only backfill, fetch everything since last backfill
86
+ return self.__to_bound(most_recent), None
87
+
88
+ most_recent_back_filled = self.__store.get_most_recent_with_legacy_id(
89
+ orm, self.room.id, ArchivedMessageSource.BACKFILL
90
+ )
91
+ if most_recent_back_filled is None:
92
+ # group was never back-filled, fetch everything before first live
93
+ least_recent_live = self.__store.get_first(orm, self.room.id, True)
94
+ assert least_recent_live is not None
95
+ return None, self.__to_bound(least_recent_live)
96
+
97
+ assert most_recent_back_filled.legacy_id is not None
98
+ least_recent_live = self.__store.get_least_recent_with_legacy_id_after(
99
+ orm, self.room.id, most_recent_back_filled.legacy_id
100
+ )
87
101
  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
- )
102
+ # this is a hole caused by slidge downtime
103
+ return self.__to_bound(most_recent_back_filled), self.__to_bound(
104
+ least_recent_live
105
+ )
99
106
 
100
107
  def get_all(
101
108
  self,
@@ -106,22 +113,24 @@ class MessageArchive:
106
113
  ids: Collection[str] = (),
107
114
  last_page_n: Optional[int] = None,
108
115
  sender: Optional[str] = None,
109
- flip=False,
116
+ flip: bool = False,
110
117
  ):
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):
118
+ with self.__store.session() as orm:
119
+ for msg in self.__store.get_messages(
120
+ orm,
121
+ self.room.id,
122
+ before_id=before_id,
123
+ after_id=after_id,
124
+ ids=ids,
125
+ last_page_n=last_page_n,
126
+ sender=sender,
127
+ start_date=start_date,
128
+ end_date=end_date,
129
+ flip=flip,
130
+ ):
131
+ yield msg
132
+
133
+ async def send_metadata(self, iq: Iq) -> None:
125
134
  """
126
135
  Send archive extent, as per the spec
127
136
 
@@ -129,7 +138,8 @@ class MessageArchive:
129
138
  :return:
130
139
  """
131
140
  reply = iq.reply()
132
- messages = self.__store.get_first_and_last(self.room_pk)
141
+ with self.__store.session() as orm:
142
+ messages = self.__store.get_first_and_last(orm, self.room.id)
133
143
  if messages:
134
144
  for x, m in [("start", messages[0]), ("end", messages[-1])]:
135
145
  reply["mam_metadata"][x]["id"] = m.id
@@ -141,7 +151,7 @@ class MessageArchive:
141
151
  reply.send()
142
152
 
143
153
 
144
- def archivable(msg: Message):
154
+ def archivable(msg: Message) -> bool:
145
155
  """
146
156
  Determine if a message stanza is worth archiving, ie, convey meaningful
147
157
  info
slidge/group/bookmarks.py CHANGED
@@ -1,16 +1,15 @@
1
1
  import abc
2
2
  import logging
3
- from typing import TYPE_CHECKING, Generic, Iterator, Optional, Type
3
+ from typing import TYPE_CHECKING, Generic, Iterator, Literal, Optional, Type, overload
4
4
 
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)
53
+
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)
56
59
 
57
- def __repr__(self):
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,59 +91,92 @@ 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
- async def by_legacy_id(self, legacy_id: LegacyGroupIdType) -> LegacyMUCType:
122
+ @overload
123
+ async def by_legacy_id(self, legacy_id: LegacyGroupIdType) -> "LegacyMUCType": ...
124
+
125
+ @overload
126
+ async def by_legacy_id(
127
+ self, legacy_id: LegacyGroupIdType, create: Literal[False]
128
+ ) -> "LegacyMUCType | None": ...
129
+
130
+ @overload
131
+ async def by_legacy_id(
132
+ self, legacy_id: LegacyGroupIdType, create: Literal[True]
133
+ ) -> "LegacyMUCType": ...
134
+
135
+ async def by_legacy_id(
136
+ self, legacy_id: LegacyGroupIdType, create: bool = False
137
+ ) -> LegacyMUCType | None:
109
138
  async with self.lock(("legacy_id", legacy_id)):
110
139
  local = await self.legacy_id_to_jid_local_part(legacy_id)
111
140
  jid = JID(f"{local}@{self.xmpp.boundjid}")
112
141
  if self.get_lock(("bare", jid.bare)):
113
- self.log.debug("Not instantiating %s after all", legacy_id)
114
- return await self.by_jid(jid)
115
-
116
- with self.__store.session():
117
- stored = self.__store.get_by_legacy_id(
118
- self.session.user_pk, str(legacy_id)
142
+ self.session.log.debug("Already updating %s via by_jid()", jid)
143
+ if create:
144
+ return await self.by_jid(jid)
145
+ else:
146
+ return self.by_jid_only_if_exists(jid)
147
+
148
+ with self.xmpp.store.session() as orm:
149
+ stored = (
150
+ orm.query(Room)
151
+ .filter_by(
152
+ user_account_id=self.session.user_pk,
153
+ legacy_id=str(legacy_id),
154
+ )
155
+ .one_or_none()
156
+ )
157
+ if stored is None:
158
+ stored = Room(
159
+ user_account_id=self.session.user_pk,
160
+ jid=jid,
161
+ legacy_id=str(legacy_id),
119
162
  )
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)
163
+ return await self.__update_if_needed(stored)
164
+
165
+ async def __update_if_needed(self, stored: Room) -> LegacyMUCType:
166
+ muc = self.from_store(stored)
167
+ if muc.stored.updated:
168
+ return muc
169
+
170
+ with muc.updating_info():
171
+ try:
172
+ await muc.update_info()
173
+ except NotImplementedError:
174
+ pass
175
+ except XMPPError:
176
+ raise
177
+ except Exception as e:
178
+ raise XMPPError("internal-server-error", str(e))
179
+
144
180
  return muc
145
181
 
146
182
  @abc.abstractmethod
@@ -164,8 +200,8 @@ class LegacyBookmarks(
164
200
  async def remove(
165
201
  self,
166
202
  muc: LegacyMUC,
167
- reason="You left this group from the official client.",
168
- kick=True,
203
+ reason: str = "You left this group from the official client.",
204
+ kick: bool = True,
169
205
  ) -> None:
170
206
  """
171
207
  Delete everything about a specific group.
@@ -179,8 +215,9 @@ class LegacyBookmarks(
179
215
  to False in case you do this somewhere else in your code, eg, on
180
216
  receiving the confirmation that the group was deleted.
181
217
  """
182
- assert muc.pk is not None
183
218
  if kick:
184
219
  user_participant = await muc.get_user_participant()
185
220
  user_participant.kick(reason)
186
- self.__store.delete(muc.pk)
221
+ with self.xmpp.store.session() as orm:
222
+ orm.delete(muc.stored)
223
+ orm.commit()