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
@@ -2,6 +2,9 @@ import logging
2
2
  from datetime import datetime
3
3
  from typing import Iterable, Optional
4
4
 
5
+ from slixmpp import Message
6
+
7
+ from ...util.archive_msg import HistoryMessage
5
8
  from ...util.types import (
6
9
  LegacyMessageType,
7
10
  LegacyThreadType,
@@ -9,6 +12,7 @@ from ...util.types import (
9
12
  MessageReference,
10
13
  ProcessingHint,
11
14
  )
15
+ from ...util.util import add_quote_prefix
12
16
  from .message_maker import MessageMaker
13
17
 
14
18
 
@@ -23,9 +27,13 @@ class TextMessageMixin(MessageMaker):
23
27
 
24
28
  def _replace_id(self, legacy_msg_id: LegacyMessageType):
25
29
  if self.mtype == "groupchat":
26
- return self.xmpp.store.sent.get_group_xmpp_id(
27
- self.session.user_pk, str(legacy_msg_id)
28
- ) or self._legacy_to_xmpp(legacy_msg_id)
30
+ with self.xmpp.store.session() as orm:
31
+ ids = self.xmpp.store.id_map.get_xmpp(
32
+ orm, self._recipient_pk(), str(legacy_msg_id), True
33
+ )
34
+ if ids:
35
+ return ids[0]
36
+ return self.session.legacy_to_xmpp_msg_id(legacy_msg_id)
29
37
  else:
30
38
  return self._legacy_to_xmpp(legacy_msg_id)
31
39
 
@@ -38,9 +46,9 @@ class TextMessageMixin(MessageMaker):
38
46
  reply_to: Optional[MessageReference] = None,
39
47
  thread: Optional[LegacyThreadType] = None,
40
48
  hints: Optional[Iterable[ProcessingHint]] = None,
41
- carbon=False,
42
- archive_only=False,
43
- correction=False,
49
+ carbon: bool = False,
50
+ archive_only: bool = False,
51
+ correction: bool = False,
44
52
  correction_event_id: Optional[LegacyMessageType] = None,
45
53
  link_previews: Optional[list[LinkPreview]] = None,
46
54
  **send_kwargs,
@@ -69,24 +77,28 @@ class TextMessageMixin(MessageMaker):
69
77
  but store it in the archive. Meant to be used during ``MUC.backfill()``
70
78
  """
71
79
  if carbon and not hasattr(self, "muc"):
72
- if not correction and self.xmpp.store.sent.was_sent_by_user(
73
- self.session.user_pk, str(legacy_msg_id)
74
- ):
75
- log.warning(
76
- "Carbon message for a message an XMPP has sent? This is a bug! %s",
77
- legacy_msg_id,
78
- )
79
- return
80
- if hasattr(self, "muc") and not self.is_user: # type:ignore
81
- log.warning(
82
- "send_text() called with carbon=True on a participant who is not the user",
83
- legacy_msg_id,
80
+ with self.xmpp.store.session() as orm:
81
+ if not correction and self.xmpp.store.id_map.was_sent_by_user(
82
+ orm, self._recipient_pk(), str(legacy_msg_id), self.is_group
83
+ ):
84
+ log.warning(
85
+ "Carbon message for a message an XMPP has sent? This is a bug! %s",
86
+ legacy_msg_id,
87
+ )
88
+ return
89
+ if hasattr(self, "muc") and not self.is_user: # type:ignore
90
+ log.warning(
91
+ "send_text() called with carbon=True on a participant who is not the user",
92
+ legacy_msg_id,
93
+ )
94
+ self.xmpp.store.id_map.set_msg(
95
+ orm,
96
+ self._recipient_pk(),
97
+ str(legacy_msg_id),
98
+ [self.session.legacy_to_xmpp_msg_id(legacy_msg_id)],
99
+ self.is_group,
84
100
  )
85
- self.xmpp.store.sent.set_message(
86
- self.session.user_pk,
87
- str(legacy_msg_id),
88
- self.session.legacy_to_xmpp_msg_id(legacy_msg_id),
89
- )
101
+ orm.commit()
90
102
  hints = self.__default_hints(hints)
91
103
  msg = self._make_message(
92
104
  mbody=body,
@@ -117,12 +129,12 @@ class TextMessageMixin(MessageMaker):
117
129
  reply_to: Optional[MessageReference] = None,
118
130
  thread: Optional[LegacyThreadType] = None,
119
131
  hints: Optional[Iterable[ProcessingHint]] = None,
120
- carbon=False,
121
- archive_only=False,
132
+ carbon: bool = False,
133
+ archive_only: bool = False,
122
134
  correction_event_id: Optional[LegacyMessageType] = None,
123
135
  link_previews: Optional[list[LinkPreview]] = None,
124
136
  **send_kwargs,
125
- ):
137
+ ) -> None:
126
138
  """
127
139
  Modify a message that was previously sent by this :term:`XMPP Entity`.
128
140
 
@@ -165,7 +177,7 @@ class TextMessageMixin(MessageMaker):
165
177
  emojis: Iterable[str] = (),
166
178
  thread: Optional[LegacyThreadType] = None,
167
179
  **kwargs,
168
- ):
180
+ ) -> None:
169
181
  """
170
182
  Send a reaction (:xep:`0444`) from this :term:`XMPP Entity`.
171
183
 
@@ -174,20 +186,45 @@ class TextMessageMixin(MessageMaker):
174
186
  :param thread:
175
187
  """
176
188
  msg = self._make_message(
177
- hints={"store"}, carbon=kwargs.get("carbon"), thread=thread
189
+ hints={"store"}, carbon=bool(kwargs.get("carbon")), thread=thread
178
190
  )
179
191
  xmpp_id = kwargs.pop("xmpp_id", None)
180
192
  if not xmpp_id:
181
193
  xmpp_id = self._legacy_to_xmpp(legacy_msg_id)
182
194
  self.xmpp["xep_0444"].set_reactions(msg, to_id=xmpp_id, reactions=emojis)
195
+ self.__add_reaction_fallback(msg, legacy_msg_id, emojis)
183
196
  self._send(msg, **kwargs)
184
197
 
198
+ def __add_reaction_fallback(
199
+ self,
200
+ msg: Message,
201
+ legacy_msg_id: LegacyMessageType,
202
+ emojis: Iterable[str] = (),
203
+ ) -> None:
204
+ if not self.session.user.preferences.get("reaction_fallback", False):
205
+ return
206
+ msg["fallback"]["for"] = self.xmpp.plugin["xep_0444"].namespace
207
+ msg["fallback"].enable("body")
208
+ msg["body"] = " ".join(emojis)
209
+ if not self.is_participant:
210
+ return
211
+ with self.xmpp.store.session() as orm:
212
+ archived = self.xmpp.store.mam.get_by_legacy_id(
213
+ orm, self.muc.stored.id, str(legacy_msg_id)
214
+ )
215
+ if archived is None:
216
+ return
217
+ history_msg = HistoryMessage(archived.stanza)
218
+ msg["body"] = (
219
+ add_quote_prefix(history_msg.stanza["body"]) + "\n" + msg["body"]
220
+ )
221
+
185
222
  def retract(
186
223
  self,
187
224
  legacy_msg_id: LegacyMessageType,
188
225
  thread: Optional[LegacyThreadType] = None,
189
226
  **kwargs,
190
- ):
227
+ ) -> None:
191
228
  """
192
229
  Send a message retraction (:XEP:`0424`) from this :term:`XMPP Entity`.
193
230
 
@@ -198,7 +235,7 @@ class TextMessageMixin(MessageMaker):
198
235
  state=None,
199
236
  hints={"store"},
200
237
  mbody=f"/me retracted the message {legacy_msg_id}",
201
- carbon=kwargs.get("carbon"),
238
+ carbon=bool(kwargs.get("carbon")),
202
239
  thread=thread,
203
240
  )
204
241
  msg.enable("fallback")
@@ -1,13 +1,20 @@
1
1
  import re
2
2
  from asyncio import Task, sleep
3
3
  from datetime import datetime, timedelta, timezone
4
- from typing import Optional
4
+ from functools import partial
5
+ from typing import TYPE_CHECKING, Optional
5
6
 
6
7
  from slixmpp.types import PresenceShows, PresenceTypes
8
+ from sqlalchemy.exc import InvalidRequestError
9
+ from sqlalchemy.orm.exc import DetachedInstanceError
7
10
 
11
+ from ...db.models import Contact, Participant
8
12
  from ...util.types import CachedPresence
9
- from .. import config
10
13
  from .base import BaseSender
14
+ from .db import DBMixin
15
+
16
+ if TYPE_CHECKING:
17
+ from ..session import BaseSession
11
18
 
12
19
 
13
20
  class _NoChange(Exception):
@@ -15,43 +22,85 @@ class _NoChange(Exception):
15
22
 
16
23
 
17
24
  _FRIEND_REQUEST_PRESENCES = {"subscribe", "unsubscribe", "subscribed", "unsubscribed"}
25
+ _UPDATE_LAST_SEEN_FALLBACK_TASKS = dict[int, Task]()
26
+ _ONE_WEEK_SECONDS = 3600 * 24 * 7
27
+
28
+
29
+ async def _update_last_seen_fallback(session: "BaseSession", contact_pk: int) -> None:
30
+ await sleep(_ONE_WEEK_SECONDS)
31
+ with session.xmpp.store.session() as orm:
32
+ stored = orm.get(Contact, contact_pk)
33
+ if stored is None:
34
+ return
35
+ contact = session.contacts.from_store(stored)
36
+ contact.send_last_presence(force=True, no_cache_online=False)
18
37
 
19
38
 
20
- class PresenceMixin(BaseSender):
39
+ def _clear_last_seen_task(contact_pk: int, _task) -> None:
40
+ try:
41
+ del _UPDATE_LAST_SEEN_FALLBACK_TASKS[contact_pk]
42
+ except KeyError:
43
+ pass
44
+
45
+
46
+ class PresenceMixin(BaseSender, DBMixin):
21
47
  _ONLY_SEND_PRESENCE_CHANGES = False
22
- contact_pk: Optional[int] = None
23
48
 
24
- def __init__(self, *a, **k):
49
+ stored: Contact | Participant
50
+
51
+ def __init__(self, *a, **k) -> None:
25
52
  super().__init__(*a, **k)
26
- # FIXME: this should not be an attribute of this mixin to allow garbage
27
- # collection of instances
28
- self.__update_last_seen_fallback_task: Optional[Task] = None
29
53
  # this is only used when a presence is set during Contact.update_info(),
30
54
  # when the contact does not have a DB primary key yet, and is written
31
55
  # to DB at the end of update_info()
32
56
  self.cached_presence: Optional[CachedPresence] = None
33
57
 
34
- async def __update_last_seen_fallback(self):
35
- await sleep(3600 * 7)
36
- self.send_last_presence(force=True, no_cache_online=False)
58
+ def __stored(self) -> Contact | None:
59
+ if isinstance(self.stored, Contact):
60
+ return self.stored
61
+ else:
62
+ try:
63
+ return self.stored.contact
64
+ except DetachedInstanceError:
65
+ with self.xmpp.store.session() as orm:
66
+ orm.add(self.stored)
67
+ return self.stored.contact
68
+
69
+ @property
70
+ def __contact_pk(self) -> int | None:
71
+ stored = self.__stored()
72
+ return None if stored is None else stored.id
37
73
 
38
74
  def _get_last_presence(self) -> Optional[CachedPresence]:
39
- if self.contact_pk is None:
75
+ stored = self.__stored()
76
+ if stored is None or not stored.cached_presence:
40
77
  return None
41
- return self.xmpp.store.contacts.get_presence(self.contact_pk)
78
+ return CachedPresence(
79
+ None
80
+ if stored.last_seen is None
81
+ else stored.last_seen.replace(tzinfo=timezone.utc),
82
+ stored.ptype, # type:ignore
83
+ stored.pstatus,
84
+ stored.pshow, # type:ignore
85
+ )
42
86
 
43
- def _store_last_presence(self, new: CachedPresence):
44
- if self.contact_pk is None:
45
- self.cached_presence = new
46
- return
47
- self.xmpp.store.contacts.set_presence(self.contact_pk, new)
87
+ def _store_last_presence(self, new: CachedPresence) -> None:
88
+ stored = self.__stored()
89
+ if stored is not None:
90
+ stored.cached_presence = True
91
+ for k, v in new._asdict().items():
92
+ setattr(stored, k, v)
93
+ try:
94
+ self.commit()
95
+ except InvalidRequestError:
96
+ self.commit(merge=True)
48
97
 
49
98
  def _make_presence(
50
99
  self,
51
100
  *,
52
101
  last_seen: Optional[datetime] = None,
53
- force=False,
54
- bare=False,
102
+ force: bool = False,
103
+ bare: bool = False,
55
104
  ptype: Optional[PresenceTypes] = None,
56
105
  pstatus: Optional[str] = None,
57
106
  pshow: Optional[PresenceShows] = None,
@@ -67,8 +116,10 @@ class PresenceMixin(BaseSender):
67
116
  )
68
117
  if old != new:
69
118
  if hasattr(self, "muc") and ptype == "unavailable":
70
- if self.contact_pk is not None:
71
- self.xmpp.store.contacts.reset_presence(self.contact_pk)
119
+ stored = self.__stored()
120
+ if stored is not None:
121
+ stored.cached_presence = False
122
+ self.commit(merge=True)
72
123
  else:
73
124
  self._store_last_presence(new)
74
125
  if old and not force and self._ONLY_SEND_PRESENCE_CHANGES:
@@ -87,27 +138,33 @@ class PresenceMixin(BaseSender):
87
138
  )
88
139
  if last_seen:
89
140
  # it's ugly to check for the presence of this string, but a better fix is more work
90
- if config.LAST_SEEN_FALLBACK and not re.match(
141
+ if not re.match(
91
142
  ".*Last seen .*", p["status"]
92
- ):
143
+ ) and self.session.user.preferences.get("last_seen_fallback", True):
93
144
  last_seen_fallback, recent = get_last_seen_fallback(last_seen)
94
145
  if p["status"]:
95
146
  p["status"] = p["status"] + " -- " + last_seen_fallback
96
147
  else:
97
148
  p["status"] = last_seen_fallback
98
- if recent:
149
+ pk = self.__contact_pk
150
+ if recent and pk is not None:
99
151
  # if less than a week, we use sth like 'Last seen: Monday, 8:05",
100
152
  # but if lasts more than a week, this is not very informative, so
101
153
  # we need to force resend an updated presence status
102
- if self.__update_last_seen_fallback_task:
103
- self.__update_last_seen_fallback_task.cancel()
104
- self.__update_last_seen_fallback_task = self.xmpp.loop.create_task(
105
- self.__update_last_seen_fallback()
154
+ task = _UPDATE_LAST_SEEN_FALLBACK_TASKS.get(pk)
155
+ if task is not None:
156
+ task.cancel()
157
+ task = self.session.create_task(
158
+ _update_last_seen_fallback(self.session, pk)
106
159
  )
160
+ _UPDATE_LAST_SEEN_FALLBACK_TASKS[pk] = task
161
+ task.add_done_callback(partial(_clear_last_seen_task, pk))
107
162
  p["idle"]["since"] = last_seen
108
163
  return p
109
164
 
110
- def send_last_presence(self, force=False, no_cache_online=False):
165
+ def send_last_presence(
166
+ self, force: bool = False, no_cache_online: bool = False
167
+ ) -> None:
111
168
  if (cache := self._get_last_presence()) is None:
112
169
  if force:
113
170
  if no_cache_online:
@@ -129,7 +186,7 @@ class PresenceMixin(BaseSender):
129
186
  self,
130
187
  status: Optional[str] = None,
131
188
  last_seen: Optional[datetime] = None,
132
- ):
189
+ ) -> None:
133
190
  """
134
191
  Send an "online" presence from this contact to the user.
135
192
 
@@ -145,7 +202,7 @@ class PresenceMixin(BaseSender):
145
202
  self,
146
203
  status: Optional[str] = None,
147
204
  last_seen: Optional[datetime] = None,
148
- ):
205
+ ) -> None:
149
206
  """
150
207
  Send an "away" presence from this contact to the user.
151
208
 
@@ -166,7 +223,7 @@ class PresenceMixin(BaseSender):
166
223
  self,
167
224
  status: Optional[str] = None,
168
225
  last_seen: Optional[datetime] = None,
169
- ):
226
+ ) -> None:
170
227
  """
171
228
  Send an "extended away" presence from this contact to the user.
172
229
 
@@ -187,7 +244,7 @@ class PresenceMixin(BaseSender):
187
244
  self,
188
245
  status: Optional[str] = None,
189
246
  last_seen: Optional[datetime] = None,
190
- ):
247
+ ) -> None:
191
248
  """
192
249
  Send a "busy" (ie, "dnd") presence from this contact to the user,
193
250
 
@@ -205,7 +262,7 @@ class PresenceMixin(BaseSender):
205
262
  self,
206
263
  status: Optional[str] = None,
207
264
  last_seen: Optional[datetime] = None,
208
- ):
265
+ ) -> None:
209
266
  """
210
267
  Send an "offline" presence from this contact to the user.
211
268
 
@@ -222,9 +279,9 @@ class PresenceMixin(BaseSender):
222
279
  pass
223
280
 
224
281
 
225
- def get_last_seen_fallback(last_seen: datetime):
282
+ def get_last_seen_fallback(last_seen: datetime) -> tuple[str, bool]:
226
283
  now = datetime.now(tz=timezone.utc)
227
284
  if now - last_seen < timedelta(days=7):
228
- return f"Last seen {last_seen:%A %H:%M GMT}", True
285
+ return f"Last seen {last_seen:%A %H:%M %p GMT}", True
229
286
  else:
230
287
  return f"Last seen {last_seen:%b %-d %Y}", False
slidge/core/pubsub.py CHANGED
@@ -21,8 +21,8 @@ from slixmpp.plugins.xep_0292.stanza import VCard4
21
21
  from slixmpp.types import JidStr, OptJidStr
22
22
 
23
23
  from ..db.avatar import CachedAvatar, avatar_cache
24
- from ..db.store import ContactStore, SlidgeStore
25
- from .mixins.lock import NamedLockMixin
24
+ from ..db.models import GatewayUser
25
+ from ..util.lock import NamedLockMixin
26
26
 
27
27
  if TYPE_CHECKING:
28
28
  from slidge.core.gateway import BaseGateway
@@ -32,14 +32,8 @@ if TYPE_CHECKING:
32
32
  VCARD4_NAMESPACE = "urn:xmpp:vcard4"
33
33
 
34
34
 
35
- class PepItem:
36
- pass
37
-
38
-
39
- class PepAvatar(PepItem):
40
- store: SlidgeStore
41
-
42
- def __init__(self):
35
+ class PepAvatar:
36
+ def __init__(self) -> None:
43
37
  self.metadata: Optional[AvatarMetadata] = None
44
38
  self.id: Optional[str] = None
45
39
  self._avatar_data_path: Optional[Path] = None
@@ -52,7 +46,7 @@ class PepAvatar(PepItem):
52
46
  data.set_value(self._avatar_data_path.read_bytes())
53
47
  return data
54
48
 
55
- def set_avatar_from_cache(self, cached_avatar: CachedAvatar):
49
+ def set_avatar_from_cache(self, cached_avatar: CachedAvatar) -> None:
56
50
  metadata = AvatarMetadata()
57
51
  self.id = cached_avatar.hash
58
52
  metadata.add_info(
@@ -66,17 +60,6 @@ class PepAvatar(PepItem):
66
60
  self._avatar_data_path = cached_avatar.path
67
61
 
68
62
 
69
- class PepNick(PepItem):
70
- contact_store: ContactStore
71
-
72
- def __init__(self, nick: Optional[str] = None):
73
- nickname = UserNick()
74
- if nick is not None:
75
- nickname["nick"] = nick
76
- self.nick = nickname
77
- self.__nick_str = nick
78
-
79
-
80
63
  class PubSubComponent(NamedLockMixin, BasePlugin):
81
64
  xmpp: "BaseGateway"
82
65
 
@@ -91,11 +74,11 @@ class PubSubComponent(NamedLockMixin, BasePlugin):
91
74
  default_config = {"component_name": None}
92
75
  component_name: str
93
76
 
94
- def __init__(self, *a, **kw):
77
+ def __init__(self, *a, **kw) -> None:
95
78
  super(PubSubComponent, self).__init__(*a, **kw)
96
79
  register_stanza_plugin(EventItem, UserNick)
97
80
 
98
- def plugin_init(self):
81
+ def plugin_init(self) -> None:
99
82
  self.xmpp.register_handler(
100
83
  CoroutineCallback(
101
84
  "pubsub_get_avatar_data",
@@ -144,7 +127,7 @@ class PubSubComponent(NamedLockMixin, BasePlugin):
144
127
 
145
128
  async def on_presence_available(
146
129
  self, p: Presence, contact: Optional["LegacyContact"]
147
- ):
130
+ ) -> None:
148
131
  if p.get_plugin("muc_join", check=True) is not None:
149
132
  log.debug("Ignoring MUC presence here")
150
133
  return
@@ -177,14 +160,16 @@ class PubSubComponent(NamedLockMixin, BasePlugin):
177
160
  except XMPPError:
178
161
  pass
179
162
  else:
180
- await self.__broadcast(data=pep_nick.nick, from_=p.get_to(), to=from_)
163
+ await self.__broadcast(data=pep_nick, from_=p.get_to(), to=from_)
181
164
 
182
165
  if contact is not None and VCARD4_NAMESPACE + "+notify" in features:
183
166
  await self.broadcast_vcard_event(
184
167
  p.get_to(), from_, await contact.get_vcard()
185
168
  )
186
169
 
187
- async def broadcast_vcard_event(self, from_: JID, to: JID, vcard: VCard4 | None):
170
+ async def broadcast_vcard_event(
171
+ self, from_: JID, to: JID, vcard: VCard4 | None
172
+ ) -> None:
188
173
  item = Item()
189
174
  item.namespace = VCARD4_NAMESPACE
190
175
  item["id"] = "current"
@@ -211,33 +196,33 @@ class PubSubComponent(NamedLockMixin, BasePlugin):
211
196
  ) -> PepAvatar:
212
197
  if stanza.get_to() == self.xmpp.boundjid.bare:
213
198
  item = PepAvatar()
214
- if hasattr(self.xmpp, "avatar_pk"):
215
- item.set_avatar_from_cache(avatar_cache.get_by_pk(self.xmpp.avatar_pk))
199
+ if self.xmpp.avatar is not None:
200
+ item.set_avatar_from_cache(self.xmpp.avatar)
216
201
  return item
217
202
 
218
203
  if contact is None:
219
204
  contact = await self.__get_contact(stanza)
220
205
 
221
206
  item = PepAvatar()
222
- if contact.avatar_pk is not None:
223
- stored = avatar_cache.get_by_pk(contact.avatar_pk)
207
+ if contact.stored.avatar is not None:
208
+ stored = avatar_cache.get(contact.stored.avatar)
224
209
  assert stored is not None
225
210
  item.set_avatar_from_cache(stored)
226
211
  return item
227
212
 
228
213
  async def _get_authorized_nick(
229
214
  self, stanza: Union[Iq, Presence], contact: Optional["LegacyContact"] = None
230
- ) -> PepNick:
215
+ ) -> UserNick:
231
216
  if stanza.get_to() == self.xmpp.boundjid.bare:
232
- return PepNick(self.xmpp.COMPONENT_NAME)
217
+ return get_user_nick(self.xmpp.COMPONENT_NAME)
233
218
 
234
219
  if contact is None:
235
220
  contact = await self.__get_contact(stanza)
236
221
 
237
222
  if contact.name is not None:
238
- return PepNick(contact.name)
223
+ return get_user_nick(contact.name)
239
224
  else:
240
- return PepNick()
225
+ return UserNick()
241
226
 
242
227
  def __reply_with(
243
228
  self, iq: Iq, content: AvatarData | AvatarMetadata | None, item_id: str | None
@@ -254,11 +239,11 @@ class PubSubComponent(NamedLockMixin, BasePlugin):
254
239
  else:
255
240
  raise XMPPError("item-not-found")
256
241
 
257
- async def _get_avatar_data(self, iq: Iq):
242
+ async def _get_avatar_data(self, iq: Iq) -> None:
258
243
  pep_avatar = await self._get_authorized_avatar(iq)
259
244
  self.__reply_with(iq, pep_avatar.data, pep_avatar.id)
260
245
 
261
- async def _get_avatar_metadata(self, iq: Iq):
246
+ async def _get_avatar_metadata(self, iq: Iq) -> None:
262
247
  pep_avatar = await self._get_authorized_avatar(iq)
263
248
  self.__reply_with(iq, pep_avatar.metadata, pep_avatar.id)
264
249
 
@@ -279,7 +264,7 @@ class PubSubComponent(NamedLockMixin, BasePlugin):
279
264
  payload: Optional[Union[AvatarMetadata, AvatarData, VCard4]],
280
265
  id_: Optional[str],
281
266
  namespace: Optional[str] = None,
282
- ):
267
+ ) -> None:
283
268
  result = iq.reply()
284
269
  item = Item()
285
270
  if payload:
@@ -291,7 +276,9 @@ class PubSubComponent(NamedLockMixin, BasePlugin):
291
276
  result["pubsub"]["items"].append(item)
292
277
  result.send()
293
278
 
294
- async def __broadcast(self, data, from_: JidStr, to: OptJidStr = None, **kwargs):
279
+ async def __broadcast(
280
+ self, data, from_: JidStr, to: OptJidStr = None, **kwargs
281
+ ) -> None:
295
282
  from_ = JID(from_)
296
283
  if from_ != self.xmpp.boundjid.bare and to is not None:
297
284
  to = JID(to)
@@ -319,10 +306,11 @@ class PubSubComponent(NamedLockMixin, BasePlugin):
319
306
  msg.append(event)
320
307
 
321
308
  if to is None:
322
- for u in self.xmpp.store.users.get_all():
323
- new_msg = copy(msg)
324
- new_msg.set_to(u.jid.bare)
325
- new_msg.send()
309
+ with self.xmpp.store.session() as orm:
310
+ for u in orm.query(GatewayUser).all():
311
+ new_msg = copy(msg)
312
+ new_msg.set_to(u.jid.bare)
313
+ new_msg.send()
326
314
  else:
327
315
  msg.set_to(to)
328
316
  msg.send()
@@ -345,11 +333,18 @@ class PubSubComponent(NamedLockMixin, BasePlugin):
345
333
  user_jid: JID,
346
334
  jid: JidStr,
347
335
  nick: Optional[str] = None,
348
- ):
336
+ ) -> None:
349
337
  jid = JID(jid)
350
- nickname = PepNick(nick)
351
- log.debug("New nickname: %s", nickname.nick)
352
- self.xmpp.loop.create_task(self.__broadcast(nickname.nick, jid, user_jid.bare))
338
+ nickname = get_user_nick(nick)
339
+ log.debug("New nickname: %s", nickname)
340
+ self.xmpp.loop.create_task(self.__broadcast(nickname, jid, user_jid.bare))
341
+
342
+
343
+ def get_user_nick(nick: Optional[str] = None) -> UserNick:
344
+ user_nick = UserNick()
345
+ if nick is not None:
346
+ user_nick["nick"] = nick
347
+ return user_nick
353
348
 
354
349
 
355
350
  log = logging.getLogger(__name__)