slidge 0.1.3__py3-none-any.whl → 0.2.0a1__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. slidge/__init__.py +3 -5
  2. slidge/__main__.py +2 -196
  3. slidge/__version__.py +5 -0
  4. slidge/command/adhoc.py +8 -1
  5. slidge/command/admin.py +6 -7
  6. slidge/command/base.py +1 -2
  7. slidge/command/register.py +32 -16
  8. slidge/command/user.py +85 -6
  9. slidge/contact/contact.py +165 -49
  10. slidge/contact/roster.py +122 -47
  11. slidge/core/config.py +14 -11
  12. slidge/core/gateway/base.py +148 -36
  13. slidge/core/gateway/caps.py +7 -5
  14. slidge/core/gateway/disco.py +2 -4
  15. slidge/core/gateway/mam.py +1 -4
  16. slidge/core/gateway/muc_admin.py +1 -1
  17. slidge/core/gateway/ping.py +2 -3
  18. slidge/core/gateway/presence.py +1 -1
  19. slidge/core/gateway/registration.py +32 -21
  20. slidge/core/gateway/search.py +3 -5
  21. slidge/core/gateway/session_dispatcher.py +120 -57
  22. slidge/core/gateway/vcard_temp.py +7 -5
  23. slidge/core/mixins/__init__.py +11 -1
  24. slidge/core/mixins/attachment.py +32 -14
  25. slidge/core/mixins/avatar.py +90 -25
  26. slidge/core/mixins/base.py +8 -2
  27. slidge/core/mixins/db.py +18 -0
  28. slidge/core/mixins/disco.py +0 -10
  29. slidge/core/mixins/message.py +18 -8
  30. slidge/core/mixins/message_maker.py +17 -9
  31. slidge/core/mixins/presence.py +17 -4
  32. slidge/core/pubsub.py +54 -220
  33. slidge/core/session.py +69 -34
  34. slidge/db/__init__.py +4 -0
  35. slidge/db/alembic/env.py +64 -0
  36. slidge/db/alembic/script.py.mako +26 -0
  37. slidge/db/alembic/versions/09f27f098baa_add_missing_attributes_in_room.py +36 -0
  38. slidge/db/alembic/versions/2461390c0af2_store_contacts_caps_verstring_in_db.py +36 -0
  39. slidge/db/alembic/versions/29f5280c61aa_store_subject_setter_in_room.py +37 -0
  40. slidge/db/alembic/versions/2b1f45ab7379_store_room_subject_setter_by_nickname.py +41 -0
  41. slidge/db/alembic/versions/82a4af84b679_add_muc_history_filled.py +48 -0
  42. slidge/db/alembic/versions/8d2ced764698_rely_on_db_to_store_contacts_rooms_and_.py +133 -0
  43. slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +85 -0
  44. slidge/db/alembic/versions/b33993e87db3_move_everything_to_persistent_db.py +214 -0
  45. slidge/db/alembic/versions/b64b1a793483_add_source_and_legacy_id_for_archived_.py +48 -0
  46. slidge/db/alembic/versions/c4a8ec35a0e8_per_room_user_nick.py +34 -0
  47. slidge/db/alembic/versions/e91195719c2c_store_users_avatars_persistently.py +26 -0
  48. slidge/db/avatar.py +235 -0
  49. slidge/db/meta.py +65 -0
  50. slidge/db/models.py +375 -0
  51. slidge/db/store.py +1078 -0
  52. slidge/group/archive.py +58 -14
  53. slidge/group/bookmarks.py +72 -57
  54. slidge/group/participant.py +87 -28
  55. slidge/group/room.py +369 -211
  56. slidge/main.py +201 -0
  57. slidge/migration.py +30 -0
  58. slidge/slixfix/__init__.py +35 -2
  59. slidge/slixfix/roster.py +11 -4
  60. slidge/slixfix/xep_0292/vcard4.py +3 -0
  61. slidge/util/archive_msg.py +2 -1
  62. slidge/util/db.py +1 -47
  63. slidge/util/test.py +71 -4
  64. slidge/util/types.py +29 -4
  65. slidge/util/util.py +22 -0
  66. {slidge-0.1.3.dist-info → slidge-0.2.0a1.dist-info}/METADATA +4 -4
  67. slidge-0.2.0a1.dist-info/RECORD +114 -0
  68. slidge/core/cache.py +0 -183
  69. slidge/util/schema.sql +0 -126
  70. slidge/util/sql.py +0 -508
  71. slidge-0.1.3.dist-info/RECORD +0 -96
  72. {slidge-0.1.3.dist-info → slidge-0.2.0a1.dist-info}/LICENSE +0 -0
  73. {slidge-0.1.3.dist-info → slidge-0.2.0a1.dist-info}/WHEEL +0 -0
  74. {slidge-0.1.3.dist-info → slidge-0.2.0a1.dist-info}/entry_points.txt +0 -0
slidge/db/models.py ADDED
@@ -0,0 +1,375 @@
1
+ import warnings
2
+ from datetime import datetime
3
+ from enum import IntEnum
4
+ from typing import Optional
5
+
6
+ import sqlalchemy as sa
7
+ from slixmpp import JID
8
+ from slixmpp.types import MucAffiliation, MucRole
9
+ from sqlalchemy import ForeignKey, Index, UniqueConstraint
10
+ from sqlalchemy.orm import Mapped, mapped_column, relationship
11
+
12
+ from ..util.types import MucType
13
+ from .meta import Base, JSONSerializable, JSONSerializableTypes
14
+
15
+
16
+ class XmppToLegacyEnum(IntEnum):
17
+ """
18
+ XMPP-client generated IDs, used in the XmppToLegacyIds table to keep track
19
+ of corresponding legacy IDs
20
+ """
21
+
22
+ DM = 1
23
+ GROUP_CHAT = 2
24
+ THREAD = 3
25
+
26
+
27
+ class ArchivedMessageSource(IntEnum):
28
+ """
29
+ Whether an archived message comes from ``LegacyMUC.backfill()`` or was received
30
+ as a "live" message.
31
+ """
32
+
33
+ LIVE = 1
34
+ BACKFILL = 2
35
+
36
+
37
+ class GatewayUser(Base):
38
+ """
39
+ A user, registered to the gateway component.
40
+ """
41
+
42
+ __tablename__ = "user_account"
43
+ id: Mapped[int] = mapped_column(primary_key=True)
44
+ jid: Mapped[JID] = mapped_column(unique=True)
45
+ registration_date: Mapped[datetime] = mapped_column(
46
+ sa.DateTime, server_default=sa.func.now()
47
+ )
48
+
49
+ legacy_module_data: Mapped[JSONSerializable] = mapped_column(default={})
50
+ """
51
+ Arbitrary non-relational data that legacy modules can use
52
+ """
53
+ preferences: Mapped[JSONSerializable] = mapped_column(default={})
54
+ avatar_hash: Mapped[Optional[str]] = mapped_column(default=None)
55
+ """
56
+ Hash of the user's avatar, to avoid re-publishing the same avatar on the
57
+ legacy network
58
+ """
59
+
60
+ contacts: Mapped[list["Contact"]] = relationship(
61
+ back_populates="user", cascade="all, delete-orphan"
62
+ )
63
+ rooms: Mapped[list["Room"]] = relationship(
64
+ back_populates="user", cascade="all, delete-orphan"
65
+ )
66
+ xmpp_to_legacy: Mapped[list["XmppToLegacyIds"]] = relationship(
67
+ cascade="all, delete-orphan"
68
+ )
69
+ attachments: Mapped[list["Attachment"]] = relationship(cascade="all, delete-orphan")
70
+ multi_legacy: Mapped[list["LegacyIdsMulti"]] = relationship(
71
+ cascade="all, delete-orphan"
72
+ )
73
+ multi_xmpp: Mapped[list["XmppIdsMulti"]] = relationship(
74
+ cascade="all, delete-orphan"
75
+ )
76
+
77
+ def __repr__(self) -> str:
78
+ return f"User(id={self.id!r}, jid={self.jid!r})"
79
+
80
+ def get(self, field: str, default: str = "") -> JSONSerializableTypes:
81
+ # """
82
+ # Get fields from the registration form (required to comply with slixmpp backend protocol)
83
+ #
84
+ # :param field: Name of the field
85
+ # :param default: Default value to return if the field is not present
86
+ #
87
+ # :return: Value of the field
88
+ # """
89
+ return self.legacy_module_data.get(field, default)
90
+
91
+ @property
92
+ def registration_form(self) -> dict:
93
+ # Kept for retrocompat, should be
94
+ # FIXME: delete me
95
+ warnings.warn(
96
+ "GatewayUser.registration_form is deprecated.", DeprecationWarning
97
+ )
98
+ return self.legacy_module_data
99
+
100
+
101
+ class Avatar(Base):
102
+ """
103
+ Avatars of contacts, rooms and participants.
104
+
105
+ To comply with XEPs, we convert them all to PNG before storing them.
106
+ """
107
+
108
+ __tablename__ = "avatar"
109
+
110
+ id: Mapped[int] = mapped_column(primary_key=True)
111
+
112
+ filename: Mapped[str] = mapped_column(unique=True)
113
+ hash: Mapped[str] = mapped_column(unique=True)
114
+ height: Mapped[int] = mapped_column()
115
+ width: Mapped[int] = mapped_column()
116
+
117
+ # legacy network-wide unique identifier for the avatar
118
+ legacy_id: Mapped[Optional[str]] = mapped_column(unique=True, nullable=True)
119
+
120
+ # this is only used when avatars are available as HTTP URLs and do not
121
+ # have a legacy_id
122
+ url: Mapped[Optional[str]] = mapped_column(default=None)
123
+ etag: Mapped[Optional[str]] = mapped_column(default=None)
124
+ last_modified: Mapped[Optional[str]] = mapped_column(default=None)
125
+
126
+ contacts: Mapped[list["Contact"]] = relationship(back_populates="avatar")
127
+ rooms: Mapped[list["Room"]] = relationship(back_populates="avatar")
128
+
129
+
130
+ class Contact(Base):
131
+ """
132
+ Legacy contacts
133
+ """
134
+
135
+ __tablename__ = "contact"
136
+ __table_args__ = (
137
+ UniqueConstraint("user_account_id", "legacy_id"),
138
+ UniqueConstraint("user_account_id", "jid"),
139
+ )
140
+
141
+ id: Mapped[int] = mapped_column(primary_key=True)
142
+ user_account_id: Mapped[int] = mapped_column(ForeignKey("user_account.id"))
143
+ user: Mapped[GatewayUser] = relationship(back_populates="contacts")
144
+ legacy_id: Mapped[str] = mapped_column(nullable=False)
145
+
146
+ jid: Mapped[JID] = mapped_column()
147
+
148
+ avatar_id: Mapped[int] = mapped_column(ForeignKey("avatar.id"), nullable=True)
149
+ avatar: Mapped[Avatar] = relationship(back_populates="contacts")
150
+
151
+ nick: Mapped[Optional[str]] = mapped_column(nullable=True)
152
+
153
+ cached_presence: Mapped[bool] = mapped_column(default=False)
154
+ last_seen: Mapped[Optional[datetime]] = mapped_column(nullable=True)
155
+ ptype: Mapped[Optional[str]] = mapped_column(nullable=True)
156
+ pstatus: Mapped[Optional[str]] = mapped_column(nullable=True)
157
+ pshow: Mapped[Optional[str]] = mapped_column(nullable=True)
158
+ caps_ver: Mapped[Optional[str]] = mapped_column(nullable=True)
159
+
160
+ is_friend: Mapped[bool] = mapped_column(default=False)
161
+ added_to_roster: Mapped[bool] = mapped_column(default=False)
162
+ sent_order: Mapped[list["ContactSent"]] = relationship(back_populates="contact")
163
+
164
+ extra_attributes: Mapped[Optional[JSONSerializable]] = mapped_column(
165
+ default=None, nullable=True
166
+ )
167
+ updated: Mapped[bool] = mapped_column(default=False)
168
+
169
+ participants: Mapped[list["Participant"]] = relationship(back_populates="contact")
170
+
171
+
172
+ class ContactSent(Base):
173
+ """
174
+ Keep track of XMPP msg ids sent by a specific contact for networks in which
175
+ all messages need to be marked as read.
176
+
177
+ (XMPP displayed markers convey a "read up to here" semantic.)
178
+ """
179
+
180
+ __tablename__ = "contact_sent"
181
+ __table_args__ = (UniqueConstraint("contact_id", "msg_id"),)
182
+
183
+ id: Mapped[int] = mapped_column(primary_key=True)
184
+ contact_id: Mapped[int] = mapped_column(ForeignKey("contact.id"))
185
+ contact: Mapped[Contact] = relationship(back_populates="sent_order")
186
+ msg_id: Mapped[str] = mapped_column()
187
+
188
+
189
+ class Room(Base):
190
+ """
191
+ Legacy room
192
+ """
193
+
194
+ __tablename__ = "room"
195
+ id: Mapped[int] = mapped_column(primary_key=True)
196
+ user_account_id: Mapped[int] = mapped_column(ForeignKey("user_account.id"))
197
+ user: Mapped[GatewayUser] = relationship(back_populates="rooms")
198
+ legacy_id: Mapped[str] = mapped_column(unique=True, nullable=False)
199
+
200
+ jid: Mapped[JID] = mapped_column(unique=True)
201
+
202
+ avatar_id: Mapped[int] = mapped_column(ForeignKey("avatar.id"), nullable=True)
203
+ avatar: Mapped[Avatar] = relationship(back_populates="rooms")
204
+
205
+ name: Mapped[Optional[str]] = mapped_column(nullable=True)
206
+ description: Mapped[Optional[str]] = mapped_column(nullable=True)
207
+ subject: Mapped[Optional[str]] = mapped_column(nullable=True)
208
+ subject_date: Mapped[Optional[datetime]] = mapped_column(nullable=True)
209
+ subject_setter: Mapped[Optional[str]] = mapped_column(nullable=True)
210
+
211
+ n_participants: Mapped[Optional[int]] = mapped_column(default=None)
212
+
213
+ muc_type: Mapped[Optional[MucType]] = mapped_column(default=MucType.GROUP)
214
+
215
+ user_nick: Mapped[Optional[str]] = mapped_column()
216
+ user_resources: Mapped[Optional[str]] = mapped_column(nullable=True)
217
+
218
+ participants_filled: Mapped[bool] = mapped_column(default=False)
219
+ history_filled: Mapped[bool] = mapped_column(default=False)
220
+
221
+ extra_attributes: Mapped[Optional[JSONSerializable]] = mapped_column(default=None)
222
+ updated: Mapped[bool] = mapped_column(default=False)
223
+
224
+ participants: Mapped[list["Participant"]] = relationship(
225
+ back_populates="room", primaryjoin="Participant.room_id == Room.id"
226
+ )
227
+
228
+
229
+ class ArchivedMessage(Base):
230
+ """
231
+ Messages of rooms, that we store to act as a MAM server
232
+ """
233
+
234
+ __tablename__ = "mam"
235
+ __table_args__ = (UniqueConstraint("room_id", "stanza_id"),)
236
+
237
+ id: Mapped[int] = mapped_column(primary_key=True)
238
+ room_id: Mapped[int] = mapped_column(ForeignKey("room.id"), nullable=False)
239
+
240
+ stanza_id: Mapped[str] = mapped_column(nullable=False)
241
+ timestamp: Mapped[datetime] = mapped_column(nullable=False)
242
+ author_jid: Mapped[JID] = mapped_column(nullable=False)
243
+ source: Mapped[ArchivedMessageSource] = mapped_column(nullable=False)
244
+ legacy_id: Mapped[Optional[str]] = mapped_column(nullable=True)
245
+
246
+ stanza: Mapped[str] = mapped_column(nullable=False)
247
+
248
+
249
+ class XmppToLegacyIds(Base):
250
+ """
251
+ XMPP-client generated IDs, and mapping to the corresponding legacy IDs
252
+ """
253
+
254
+ __tablename__ = "xmpp_to_legacy_ids"
255
+ __table_args__ = (
256
+ Index("xmpp_legacy", "user_account_id", "xmpp_id", "legacy_id", unique=True),
257
+ )
258
+ id: Mapped[int] = mapped_column(primary_key=True)
259
+ user_account_id: Mapped[int] = mapped_column(ForeignKey("user_account.id"))
260
+ user: Mapped[GatewayUser] = relationship(back_populates="xmpp_to_legacy")
261
+
262
+ xmpp_id: Mapped[str] = mapped_column(nullable=False)
263
+ legacy_id: Mapped[str] = mapped_column(nullable=False)
264
+
265
+ type: Mapped[XmppToLegacyEnum] = mapped_column(nullable=False)
266
+
267
+
268
+ class Attachment(Base):
269
+ """
270
+ Legacy attachments
271
+ """
272
+
273
+ __tablename__ = "attachment"
274
+
275
+ id: Mapped[int] = mapped_column(primary_key=True)
276
+ user_account_id: Mapped[int] = mapped_column(ForeignKey("user_account.id"))
277
+ user: Mapped[GatewayUser] = relationship(back_populates="attachments")
278
+
279
+ legacy_file_id: Mapped[Optional[str]] = mapped_column(index=True, nullable=True)
280
+ url: Mapped[str] = mapped_column(index=True, nullable=False)
281
+ sims: Mapped[Optional[str]] = mapped_column()
282
+ sfs: Mapped[Optional[str]] = mapped_column()
283
+
284
+
285
+ class LegacyIdsMulti(Base):
286
+ """
287
+ Legacy messages with multiple attachments are split as several XMPP messages,
288
+ this table and the next maps a single legacy ID to multiple XMPP IDs.
289
+ """
290
+
291
+ __tablename__ = "legacy_ids_multi"
292
+ __table_args__ = (
293
+ Index(
294
+ "legacy_ids_multi_user_account_id_legacy_id",
295
+ "user_account_id",
296
+ "legacy_id",
297
+ unique=True,
298
+ ),
299
+ )
300
+ id: Mapped[int] = mapped_column(primary_key=True)
301
+ user_account_id: Mapped[int] = mapped_column(ForeignKey("user_account.id"))
302
+
303
+ legacy_id: Mapped[str] = mapped_column(nullable=False)
304
+ xmpp_ids: Mapped[list["XmppIdsMulti"]] = relationship(
305
+ back_populates="legacy_ids_multi"
306
+ )
307
+
308
+
309
+ class XmppIdsMulti(Base):
310
+ __tablename__ = "xmpp_ids_multi"
311
+ __table_args__ = (
312
+ Index(
313
+ "legacy_ids_multi_user_account_id_xmpp_id",
314
+ "user_account_id",
315
+ "xmpp_id",
316
+ unique=True,
317
+ ),
318
+ )
319
+ id: Mapped[int] = mapped_column(primary_key=True)
320
+ user_account_id: Mapped[int] = mapped_column(ForeignKey("user_account.id"))
321
+
322
+ xmpp_id: Mapped[str] = mapped_column(nullable=False)
323
+
324
+ legacy_ids_multi_id: Mapped[int] = mapped_column(ForeignKey("legacy_ids_multi.id"))
325
+ legacy_ids_multi: Mapped[LegacyIdsMulti] = relationship(back_populates="xmpp_ids")
326
+
327
+
328
+ participant_hats = sa.Table(
329
+ "participant_hats",
330
+ Base.metadata,
331
+ sa.Column("participant_id", ForeignKey("participant.id"), primary_key=True),
332
+ sa.Column("hat_id", ForeignKey("hat.id"), primary_key=True),
333
+ )
334
+
335
+
336
+ class Hat(Base):
337
+ __tablename__ = "hat"
338
+ __table_args__ = (UniqueConstraint("title", "uri"),)
339
+
340
+ id: Mapped[int] = mapped_column(primary_key=True)
341
+ title: Mapped[str] = mapped_column()
342
+ uri: Mapped[str] = mapped_column()
343
+ participants: Mapped[list["Participant"]] = relationship(
344
+ secondary=participant_hats, back_populates="hats"
345
+ )
346
+
347
+
348
+ class Participant(Base):
349
+ __tablename__ = "participant"
350
+
351
+ id: Mapped[int] = mapped_column(primary_key=True)
352
+
353
+ room_id: Mapped[int] = mapped_column(ForeignKey("room.id"), nullable=False)
354
+ room: Mapped[Room] = relationship(
355
+ back_populates="participants", primaryjoin=Room.id == room_id
356
+ )
357
+
358
+ contact_id: Mapped[int] = mapped_column(ForeignKey("contact.id"), nullable=True)
359
+ contact: Mapped[Contact] = relationship(back_populates="participants")
360
+
361
+ is_user: Mapped[bool] = mapped_column(default=False)
362
+
363
+ affiliation: Mapped[MucAffiliation] = mapped_column(default="member")
364
+ role: Mapped[MucRole] = mapped_column(default="participant")
365
+
366
+ presence_sent: Mapped[bool] = mapped_column(default=False)
367
+
368
+ resource: Mapped[Optional[str]] = mapped_column(default=None)
369
+ nickname: Mapped[str] = mapped_column(nullable=True, default=None)
370
+
371
+ hats: Mapped[list["Hat"]] = relationship(
372
+ secondary=participant_hats, back_populates="participants"
373
+ )
374
+
375
+ extra_attributes: Mapped[Optional[JSONSerializable]] = mapped_column(default=None)